From bd967bfd1f90331cdd3ca5fc75b3e4cf0b135913 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 18 Jan 2023 19:22:58 -0800 Subject: [PATCH 01/45] wip --- packages/protocol/contracts/L1/TaikoData.sol | 1 + .../protocol/contracts/L1/libs/LibProving.sol | 60 +++--- .../contracts/libs/LibSharedConfig.sol | 3 +- .../contracts/test/L1/TestTaikoL1.sol | 1 + .../test/L1/TestTaikoL1EnableTokenomics.sol | 3 +- .../contracts/test/L1/TestTaikoL2.sol | 1 + .../L1/TestTaikoL2EnablePublicInputsCheck.sol | 1 + .../test/tokenomics/Tokenomics.test.ts | 189 ++++++++++++------ packages/protocol/test/utils/prove.ts | 47 ++--- packages/protocol/test/utils/prover.ts | 27 ++- 10 files changed, 193 insertions(+), 140 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index d4eb72cf57..b2d092aaec 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -42,6 +42,7 @@ library TaikoData { uint64 initialUncleDelay; bool enableTokenomics; bool enablePublicInputsCheck; + bool enableProofValidation; } struct BlockMetadata { diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index dc384a256c..6410482680 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -73,7 +73,11 @@ library LibProving { "L1:proof:size" ); - { + IProofVerifier proofVerifier = IProofVerifier( + resolver.resolve("proof_verifier") + ); + + if (config.enableProofValidation) { // Check anchor tx is valid LibTxDecoder.Tx memory _tx = LibTxDecoder.decodeTx( config.chainId, @@ -104,38 +108,34 @@ library LibProving { ), "L1:anchor:calldata" ); - } - - IProofVerifier proofVerifier = IProofVerifier( - resolver.resolve("proof_verifier") - ); - // Check anchor tx is the 1st tx in the block - require( - proofVerifier.verifyMKP({ - key: LibRLPWriter.writeUint(0), - value: anchorTx, - proof: evidence.proofs[zkProofsPerBlock], - root: evidence.header.transactionsRoot - }), - "L1:tx:proof" - ); + // Check anchor tx is the 1st tx in the block + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorTx, + proof: evidence.proofs[zkProofsPerBlock], + root: evidence.header.transactionsRoot + }), + "L1:tx:proof" + ); - // Check anchor tx does not throw + // Check anchor tx does not throw - LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder - .decodeReceipt(anchorReceipt); + LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder + .decodeReceipt(anchorReceipt); - require(receipt.status == 1, "L1:receipt:status"); - require( - proofVerifier.verifyMKP({ - key: LibRLPWriter.writeUint(0), - value: anchorReceipt, - proof: evidence.proofs[zkProofsPerBlock + 1], - root: evidence.header.receiptsRoot - }), - "L1:receipt:proof" - ); + require(receipt.status == 1, "L1:receipt:status"); + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorReceipt, + proof: evidence.proofs[zkProofsPerBlock + 1], + root: evidence.header.receiptsRoot + }), + "L1:receipt:proof" + ); + } // ZK-prove block and mark block proven to be valid. _proveBlock({ @@ -266,7 +266,7 @@ library LibProving { prover: evidence.prover, target: target, parentHash: evidence.header.parentHash, - blockHash: blockHashOverride == 0 ? blockHash : blockHashOverride + blockHash: blockHashOverride == 0 ? bytes32(0) : blockHashOverride }); } diff --git a/packages/protocol/contracts/libs/LibSharedConfig.sol b/packages/protocol/contracts/libs/LibSharedConfig.sol index 100b4034a5..776e287a3f 100644 --- a/packages/protocol/contracts/libs/LibSharedConfig.sol +++ b/packages/protocol/contracts/libs/LibSharedConfig.sol @@ -44,7 +44,8 @@ library LibSharedConfig { bootstrapDiscountHalvingPeriod: 180 days, initialUncleDelay: 60 minutes, enableTokenomics: false, - enablePublicInputsCheck: true + enablePublicInputsCheck: true, + enableProofValidation: true }); } } diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index ec8c6b9a6b..24a91501b6 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -51,6 +51,7 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.initialUncleDelay = 1 minutes; config.enableTokenomics = false; config.enablePublicInputsCheck = true; + config.enableProofValidation = true; } function verifyZKP( diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index abb263791a..582ac4b9a2 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -20,7 +20,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { { config.chainId = 167; // up to 2048 pending blocks - config.maxNumBlocks = 2048; + config.maxNumBlocks = 6; config.blockHashHistory = 3; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 @@ -51,6 +51,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { config.initialUncleDelay = 1 seconds; config.enableTokenomics = true; config.enablePublicInputsCheck = false; + config.enableProofValidation = false; } function verifyZKP( diff --git a/packages/protocol/contracts/test/L1/TestTaikoL2.sol b/packages/protocol/contracts/test/L1/TestTaikoL2.sol index 16037e5d6e..e2788b66f0 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL2.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL2.sol @@ -52,5 +52,6 @@ contract TestTaikoL2 is TaikoL2 { config.initialUncleDelay = 1 minutes; config.enableTokenomics = true; config.enablePublicInputsCheck = false; + config.enableProofValidation = false; } } diff --git a/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol b/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol index 8b1ebf7e45..f8c3a9f3f0 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol @@ -52,5 +52,6 @@ contract TestTaikoL2EnablePublicInputsCheck is TaikoL2 { config.initialUncleDelay = 1 minutes; config.enableTokenomics = true; config.enablePublicInputsCheck = true; + config.enableProofValidation = true; } } diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 99927ca363..f24080ff22 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -1,10 +1,12 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import { ethers as hardhatEthers } from "hardhat"; -import { TaikoL1, TaikoL2 } from "../../typechain"; +import { ConfigManager, TaikoL1, TaikoL2 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import deployAddressManager from "../utils/addressManager"; +import { BlockMetadata } from "../utils/block_metadata"; import Proposer from "../utils/proposer"; +import Prover from "../utils/prover"; // import Prover from "../utils/prover"; import { getDefaultL2Signer, @@ -86,6 +88,17 @@ describe("tokenomics", function () { taikoL1.address ); + const configManager: ConfigManager = await ( + await hardhatEthers.getContractFactory("ConfigManager") + ) + .connect(l1Signer) + .deploy(); + + await l1AddressManager.setAddress( + `${chainId}.config_manager`, + configManager.address + ); + await tkoTokenL1 .connect(l1Signer) .mintAnyone( @@ -201,69 +214,113 @@ describe("tokenomics", function () { expect(blockFee.eq(0)).to.be.eq(true); }); - // it("propose blocks and prove blocks on interval, proverReward should decline and blockFee should increase", async function () { - // const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - // const blockIdsToNumber: any = {}; - - // const proposer = new Proposer( - // taikoL1.connect(proposerSigner), - // l2Provider, - // commitConfirmations.toNumber(), - // maxNumBlocks.toNumber(), - // 0 - // ); - - // const prover = new Prover( - // taikoL1, - // taikoL2, - // l1Provider, - // l2Provider, - // proverSigner - // ); - - // let hasFailedAssertions: boolean = false; - // l2Provider.on("block", async (blockNumber) => { - // if (blockNumber <= genesisHeight) return; - // try { - // await expect( - // onNewL2Block( - // l2Provider, - // blockNumber, - // proposer, - // blockIdsToNumber, - // taikoL1, - // proposerSigner, - // tkoTokenL1 - // ) - // ).not.to.throw; - // } catch (e) { - // hasFailedAssertions = true; - // console.error(e); - // throw e; - // } - // }); - - // taikoL1.on( - // "BlockProposed", - // async (id: BigNumber, meta: BlockMetadata) => { - // console.log("proving block: id", id.toString()); - // try { - // await prover.prove( - // await proverSigner.getAddress(), - // id.toNumber(), - // blockIdsToNumber[id.toString()], - // meta - // ); - // } catch (e) { - // hasFailedAssertions = true; - // console.error(e); - // throw e; - // } - // } - // ); - - // await sleep(20 * 1000); - - // expect(hasFailedAssertions).to.be.eq(false); - // }); + it.only("propose blocks and prove blocks on interval, proverReward should decline and blockFee should increase", async function () { + const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); + const blockIdsToNumber: any = {}; + + const proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + commitConfirmations.toNumber(), + maxNumBlocks.toNumber(), + 0 + ); + + const prover = new Prover( + taikoL1, + taikoL2, + l1Provider, + l2Provider, + proverSigner + ); + + let hasFailedAssertions: boolean = false; + let blocksProposed: number = 0; + + l2Provider.on("block", async (blockNumber) => { + if (blockNumber <= genesisHeight) return; + // fill up all slots. + if (blocksProposed === maxNumBlocks.toNumber()) { + console.log("max blocks proposed!"); + l2Provider.off("block"); + return; + } + + try { + await expect( + onNewL2Block( + l2Provider, + blockNumber, + proposer, + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ) + ).not.to.throw; + blocksProposed++; + } catch (e) { + hasFailedAssertions = true; + console.error(e); + throw e; + } + }); + + let lastProofReward: BigNumber = BigNumber.from(0); + let blocksProved: number = 0; + + taikoL1.on( + "BlockProposed", + async (id: BigNumber, meta: BlockMetadata) => { + console.log("proving block: id", id.toString()); + while (blocksProposed < maxNumBlocks.toNumber()) { + await sleep(3 * 1000); + } + try { + await prover.prove( + await proverSigner.getAddress(), + id.toNumber(), + blockIdsToNumber[id.toString()], + meta + ); + + console.log("block proven: id", id.toString()); + const newProofReward = await taikoL1.getProofReward( + new Date().getMilliseconds(), + meta.timestamp + ); + // proof rewards hould shrink as no new blocks are proposed but proofs keep + // coming in, increasin availabel slots + console.log( + "last proof reward", + lastProofReward.toString(), + "new proof reward", + newProofReward.toString() + ); + blocksProved++; + if (lastProofReward.gt(0)) { + expect(newProofReward).to.be.lt(lastProofReward); + } + lastProofReward = newProofReward; + } catch (e) { + hasFailedAssertions = true; + console.error("proving error", e); + throw e; + } + } + ); + + /* eslint-disable-next-line */ + while (blocksProved < maxNumBlocks.toNumber() - 1) { + console.log( + "blocksProved: ", + blocksProved, + "maxNumblocks: ", + maxNumBlocks + ); + await sleep(1 * 1000); + } + + expect(hasFailedAssertions).to.be.eq(false); + }); }); diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index 3ee00ec7e8..b956f1fec2 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -1,5 +1,4 @@ import { ethers } from "ethers"; -import RLP from "rlp"; import { TaikoL1, TaikoL2 } from "../../typechain"; import { BlockMetadata } from "./block_metadata"; import { encodeEvidence } from "./encoding"; @@ -19,8 +18,9 @@ const buildProveBlockInputs = ( meta: meta, header: header, prover: prover, - proofs: [], // TODO + proofs: [], }; + // we have mkp + zkp returnign true in testing, so can just push 0xff // instead of actually making proofs for anchor tx, anchor receipt, and // zkp @@ -47,44 +47,27 @@ const proveBlock = async ( ) => { const config = await taikoL1.getConfig(); const header = await getBlockHeader(l2Provider, blockNumber); - - const anchorTxPopulated = await taikoL2.populateTransaction.anchor( - meta.l1Height, - meta.l1Hash, - { - gasPrice: ethers.utils.parseUnits("5", "gwei"), - gasLimit: config.anchorTxGasLimit, - } - ); - - delete anchorTxPopulated.from; - - const anchorTxSigned = await l2Signer.signTransaction(anchorTxPopulated); - - const anchorTx = await l2Provider.sendTransaction(anchorTxSigned); - - await anchorTx.wait(); - - const anchorReceipt = await anchorTx.wait(1); - - const anchorTxRLPEncoded = RLP.encode( - ethers.utils.serializeTransaction(anchorTxPopulated) - ); - - const anchorReceiptRLPEncoded = RLP.encode( - ethers.utils.serializeTransaction(anchorReceipt) - ); + header.blockHeader.difficulty = 0; + header.blockHeader.gasLimit = config.anchorTxGasLimit + .add(header.blockHeader.gasLimit) + .toNumber(); + header.blockHeader.timestamp = meta.timestamp; + // cant prove non-0 blocks + if (header.blockHeader.gasUsed <= 0) { + header.blockHeader.gasUsed = 1; + } + header.blockHeader.mixHash = meta.mixHash; + header.blockHeader.extraData = meta.extraData; const inputs = buildProveBlockInputs( meta, header.blockHeader, proverAddress, - anchorTxRLPEncoded, - anchorReceiptRLPEncoded, + "0x", + "0x", config.zkProofsPerBlock.toNumber() ); const tx = await taikoL1.proveBlock(blockId, inputs); - console.log("Proved block tx", tx.hash); const receipt = await tx.wait(1); return receipt; }; diff --git a/packages/protocol/test/utils/prover.ts b/packages/protocol/test/utils/prover.ts index 4ca2f2c713..520d2a6966 100644 --- a/packages/protocol/test/utils/prover.ts +++ b/packages/protocol/test/utils/prover.ts @@ -37,16 +37,23 @@ class Prover { } this.provingMutex = true; - await proveBlock( - this.taikoL1, - this.taikoL2, - this.l2Signer, - this.l2Provider, - proverAddress, - blockId, - blockNumber, - meta - ); + try { + await proveBlock( + this.taikoL1, + this.taikoL2, + this.l2Signer, + this.l2Provider, + proverAddress, + blockId, + blockNumber, + meta + ); + } catch (e) { + console.error("prove error", e); + throw e; + } finally { + this.provingMutex = false; + } } } From 9dba09f78bd8c3a38b95411a7a06ae25c330f829 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 20 Jan 2023 14:48:16 -0800 Subject: [PATCH 02/45] wip --- .../protocol/contracts/L1/libs/LibProving.sol | 2 +- .../test/tokenomics/Tokenomics.test.ts | 98 ++++++++++++++----- packages/protocol/test/tokenomics/utils.ts | 17 ---- packages/protocol/test/utils/proposer.ts | 3 - packages/protocol/test/utils/prove.ts | 12 ++- packages/protocol/test/utils/prover.ts | 8 +- packages/protocol/test/utils/verify.ts | 13 +++ 7 files changed, 102 insertions(+), 51 deletions(-) create mode 100644 packages/protocol/test/utils/verify.ts diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 65a2b03324..0d092ab81d 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -267,7 +267,7 @@ library LibProving { prover: evidence.prover, target: target, parentHash: evidence.header.parentHash, - blockHash: blockHashOverride == 0 ? bytes32(0) : blockHashOverride + blockHash: blockHashOverride == 0 ? blockHash : blockHashOverride }); } diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index f24080ff22..a683a64628 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import { ethers as hardhatEthers } from "hardhat"; import { ConfigManager, TaikoL1, TaikoL2 } from "../../typechain"; +import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import deployAddressManager from "../utils/addressManager"; import { BlockMetadata } from "../utils/block_metadata"; @@ -18,6 +19,7 @@ import sleep from "../utils/sleep"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; +import verifyBlocks from "../utils/verify"; import { onNewL2Block, sendTinyEtherToZeroAddress } from "./utils"; describe("tokenomics", function () { @@ -126,6 +128,11 @@ describe("tokenomics", function () { await tx.wait(1); }); + it("expects the blockFee to go be 0 when no periods have passed", async function () { + const blockFee = await taikoL1.getBlockFee(); + expect(blockFee.eq(0)).to.be.eq(true); + }); + it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since slots are growing and no proofs have been submitted", async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); // wait for one period of halving to occur, so fee is not 0. @@ -209,12 +216,7 @@ describe("tokenomics", function () { } }); - it("expects the blockFee to go be 0 when no periods have passed", async function () { - const blockFee = await taikoL1.getBlockFee(); - expect(blockFee.eq(0)).to.be.eq(true); - }); - - it.only("propose blocks and prove blocks on interval, proverReward should decline and blockFee should increase", async function () { + it.only("propose blocks, wait til maxNumBlocks is filled, proverReward should decline and blockFee should increase as blocks are proved then verified", async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); const blockIdsToNumber: any = {}; @@ -266,42 +268,53 @@ describe("tokenomics", function () { } }); - let lastProofReward: BigNumber = BigNumber.from(0); let blocksProved: number = 0; + type BlockInfo = { proposedAt: number; provenAt: number; id: number }; + + const blockInfo: BlockInfo[] = []; + taikoL1.on( "BlockProposed", async (id: BigNumber, meta: BlockMetadata) => { console.log("proving block: id", id.toString()); + + // wait until we fill up all slots, so we can + // then prove blocks in order, and each time a block is proven then verified, + // we can expect the proofReward to go down as slots become available. while (blocksProposed < maxNumBlocks.toNumber()) { await sleep(3 * 1000); } + console.log("ready to prove"); try { - await prover.prove( + const event: BlockProvenEvent = await prover.prove( await proverSigner.getAddress(), id.toNumber(), blockIdsToNumber[id.toString()], meta ); - console.log("block proven: id", id.toString()); - const newProofReward = await taikoL1.getProofReward( - new Date().getMilliseconds(), - meta.timestamp + const delay = await taikoL1.getUncleProofDelay( + event.args.id ); - // proof rewards hould shrink as no new blocks are proposed but proofs keep - // coming in, increasin availabel slots + console.log( - "last proof reward", - lastProofReward.toString(), - "new proof reward", - newProofReward.toString() + "block proven", + event.args.id, + "parent hash", + event.args.parentHash, + "delay:", + delay.toNumber() ); + + const proposedBlock = await taikoL1.getProposedBlock(id); + + blockInfo.push({ + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: event.args.provenAt.toNumber(), + id: event.args.id.toNumber(), + }); blocksProved++; - if (lastProofReward.gt(0)) { - expect(newProofReward).to.be.lt(lastProofReward); - } - lastProofReward = newProofReward; } catch (e) { hasFailedAssertions = true; console.error("proving error", e); @@ -310,17 +323,50 @@ describe("tokenomics", function () { } ); + expect(hasFailedAssertions).to.be.eq(false); + + // wait for all blocks to be proven /* eslint-disable-next-line */ while (blocksProved < maxNumBlocks.toNumber() - 1) { console.log( - "blocksProved: ", + "waiting", + blocksProposed, blocksProved, - "maxNumblocks: ", - maxNumBlocks + maxNumBlocks.toNumber() ); await sleep(1 * 1000); } - expect(hasFailedAssertions).to.be.eq(false); + console.log("done"); + + let lastProofReward: BigNumber = BigNumber.from(0); + + // now try to verify the blocks and make sure the proof reward shrinks as slots + // free up + for (let i = 1; i < blockInfo.length + 1; i++) { + const block = blockInfo.find((b) => b.id === i) as any as BlockInfo; + expect(block).not.to.be.undefined; + // verify blocks 1 by 1 + const latestL2hash = await taikoL1.getLatestSyncedHeader(); + console.log("latest synced header", latestL2hash); + + const stateVariables = await taikoL1.getStateVariables(); + console.log("latest verified block id", stateVariables[9]); + + const verifiedEvent = await verifyBlocks(taikoL1, 1); + expect(verifiedEvent).to.be.not.undefined; + + console.log("block verified", verifiedEvent.args.id); + + const newProofReward = await taikoL1.getProofReward( + block.proposedAt, + block.provenAt + ); + + if (lastProofReward.gt(0)) { + expect(newProofReward).to.be.lt(lastProofReward); + } + lastProofReward = newProofReward; + } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index b2d56776b4..9d55c788ae 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -32,29 +32,12 @@ async function onNewL2Block( meta.timestamp ); - console.log( - "NEW PROOF REWARD", - ethers.utils.formatEther(newProofReward.toString()), - " TKO" - ); - const newProposerTkoBalance = await tkoTokenL1.balanceOf( await proposerSigner.getAddress() ); - console.log( - "NEW PROPOSER TKO BALANCE", - ethers.utils.formatEther(newProposerTkoBalance.toString()), - " TKO" - ); - const newBlockFee = await taikoL1.getBlockFee(); - console.log( - "NEW BLOCK FEE", - ethers.utils.formatEther(newBlockFee.toString()), - " TKO" - ); return { newProposerTkoBalance, newBlockFee, newProofReward }; } diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index 1bb2c98f10..bda263f600 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -34,7 +34,6 @@ class Proposer { this.proposingMutex = true; if (!block) block = await this.l2Provider.getBlock("latest"); const commitSlot = this.nextCommitSlot++; - console.log("commiting ", block.number, "with commit slot", commitSlot); const { tx, commit } = await commitBlock( this.taikoL1, block, @@ -42,8 +41,6 @@ class Proposer { ); const commitReceipt = await tx.wait(this.commitConfirms ?? 1); - console.log("proposing", block.number, "with commit slot", commitSlot); - const receipt = await proposeBlock( this.taikoL1, block, diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index b956f1fec2..f289e3de60 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -1,5 +1,6 @@ import { ethers } from "ethers"; import { TaikoL1, TaikoL2 } from "../../typechain"; +import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { encodeEvidence } from "./encoding"; import Evidence from "./evidence"; @@ -44,7 +45,7 @@ const proveBlock = async ( blockId: number, blockNumber: number, meta: BlockMetadata -) => { +): Promise => { const config = await taikoL1.getConfig(); const header = await getBlockHeader(l2Provider, blockNumber); header.blockHeader.difficulty = 0; @@ -58,6 +59,10 @@ const proveBlock = async ( } header.blockHeader.mixHash = meta.mixHash; header.blockHeader.extraData = meta.extraData; + console.log( + "proving block header parent hash", + header.blockHeader.parentHash + ); const inputs = buildProveBlockInputs( meta, @@ -69,7 +74,10 @@ const proveBlock = async ( ); const tx = await taikoL1.proveBlock(blockId, inputs); const receipt = await tx.wait(1); - return receipt; + const event: BlockProvenEvent = (receipt.events as any[]).find( + (e) => e.event === "BlockProven" + ); + return event; }; export { buildProveBlockInputs, proveBlock }; diff --git a/packages/protocol/test/utils/prover.ts b/packages/protocol/test/utils/prover.ts index 520d2a6966..35e0626a30 100644 --- a/packages/protocol/test/utils/prover.ts +++ b/packages/protocol/test/utils/prover.ts @@ -1,5 +1,6 @@ import { ethers } from "ethers"; import { TaikoL1, TaikoL2 } from "../../typechain"; +import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { proveBlock } from "./prove"; import sleep from "./sleep"; @@ -31,14 +32,15 @@ class Prover { blockId: number, blockNumber: number, meta: BlockMetadata - ) { + ): Promise { while (this.provingMutex) { await sleep(100); } this.provingMutex = true; + let blockProvenEvent: BlockProvenEvent; try { - await proveBlock( + blockProvenEvent = await proveBlock( this.taikoL1, this.taikoL2, this.l2Signer, @@ -54,6 +56,8 @@ class Prover { } finally { this.provingMutex = false; } + + return blockProvenEvent; } } diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts new file mode 100644 index 0000000000..a5abae30f7 --- /dev/null +++ b/packages/protocol/test/utils/verify.ts @@ -0,0 +1,13 @@ +import { TaikoL1 } from "../../typechain"; +import { BlockVerifiedEvent } from "../../typechain/LibVerifying"; + +async function verifyBlocks(taikoL1: TaikoL1, num: number) { + const verifyTx = await taikoL1.verifyBlocks(1); + const verifyReceipt = await verifyTx.wait(1); + const verifiedEvent: BlockVerifiedEvent = ( + verifyReceipt.events as any[] + ).find((e) => e.event === "BlockVerified"); + return verifiedEvent; +} + +export default verifyBlocks; From f5c9a96905111e32cd40378d6f32e293d9ed0753 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 20 Jan 2023 22:06:06 -0800 Subject: [PATCH 03/45] wip: verification largely working --- packages/protocol/contracts/L1/TaikoL1.sol | 20 ++++++ .../contracts/L1/libs/LibVerifying.sol | 6 +- .../test/tokenomics/Tokenomics.test.ts | 65 +++++++++++++++---- packages/protocol/test/utils/verify.ts | 4 +- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 580137155e..1b895a2603 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -285,6 +285,13 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { return state.forkChoices[id][parentHash].provers; } + function getForkChoice( + uint256 id, + bytes32 parentHash + ) public view returns (TaikoData.ForkChoice memory) { + return state.forkChoices[id][parentHash]; + } + function getUncleProofDelay(uint256 blockId) public view returns (uint64) { return LibUtils.getUncleProofDelay(state, getConfig(), blockId); } @@ -292,4 +299,17 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { function getConfig() public pure virtual returns (TaikoData.Config memory) { return LibSharedConfig.getConfig(); } + + function isBlockVerifiable( + uint256 blockId, + bytes32 parentHash + ) public view returns (bool) { + return + LibVerifying.isVerifiable({ + state: state, + config: getConfig(), + fc: state.forkChoices[blockId][parentHash], + blockId: blockId + }); + } } diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 2403ca310b..6b74caba7e 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -77,7 +77,7 @@ library LibVerifying { // Uncle proof can not take more than 2x time the first proof did. if ( - !_isVerifiable({ + !isVerifiable({ state: state, config: config, fc: fc, @@ -231,12 +231,12 @@ library LibVerifying { delete fc.provers; } - function _isVerifiable( + function isVerifiable( TaikoData.State storage state, TaikoData.Config memory config, TaikoData.ForkChoice storage fc, uint256 blockId - ) private view returns (bool) { + ) public view returns (bool) { return fc.blockHash != 0 && block.timestamp > diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index a683a64628..26c6e17876 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -216,7 +216,10 @@ describe("tokenomics", function () { } }); - it.only("propose blocks, wait til maxNumBlocks is filled, proverReward should decline and blockFee should increase as blocks are proved then verified", async function () { + it.only(`propose blocks, wait til maxNumBlocks is filled. + proverReward should decline should increase as blocks are proved then verified. + the provers TKO balance should increase as the blocks are verified and + they receive the proofReward.`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); const blockIdsToNumber: any = {}; @@ -270,7 +273,12 @@ describe("tokenomics", function () { let blocksProved: number = 0; - type BlockInfo = { proposedAt: number; provenAt: number; id: number }; + type BlockInfo = { + proposedAt: number; + provenAt: number; + id: number; + parentHash: string; + }; const blockInfo: BlockInfo[] = []; @@ -313,6 +321,7 @@ describe("tokenomics", function () { proposedAt: proposedBlock.proposedAt.toNumber(), provenAt: event.args.provenAt.toNumber(), id: event.args.id.toNumber(), + parentHash: event.args.parentHash, }); blocksProved++; } catch (e) { @@ -328,36 +337,66 @@ describe("tokenomics", function () { // wait for all blocks to be proven /* eslint-disable-next-line */ while (blocksProved < maxNumBlocks.toNumber() - 1) { - console.log( - "waiting", - blocksProposed, - blocksProved, - maxNumBlocks.toNumber() - ); await sleep(1 * 1000); } - console.log("done"); - let lastProofReward: BigNumber = BigNumber.from(0); // now try to verify the blocks and make sure the proof reward shrinks as slots // free up for (let i = 1; i < blockInfo.length + 1; i++) { + await sleep(30 * 1000); + console.log("verifying block", i); const block = blockInfo.find((b) => b.id === i) as any as BlockInfo; expect(block).not.to.be.undefined; + + const isVerifiable = await taikoL1.isBlockVerifiable( + block.id, + block.parentHash + ); + console.log("block id ", block.id, "isVerifiable:", isVerifiable); + expect(isVerifiable).to.be.eq(true); + // verify blocks 1 by 1 const latestL2hash = await taikoL1.getLatestSyncedHeader(); console.log("latest synced header", latestL2hash); - const stateVariables = await taikoL1.getStateVariables(); - console.log("latest verified block id", stateVariables[9]); + const forkChoice = await taikoL1.getForkChoice( + block.id, + block.parentHash + ); + + const prover = forkChoice.provers[0]; + + const proverTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(prover); - const verifiedEvent = await verifyBlocks(taikoL1, 1); + let stateVariables = await taikoL1.getStateVariables(); + console.log( + "latest verified block id", + stateVariables[8].toNumber() + ); + + const verifiedEvent = await verifyBlocks(taikoL1, 5); expect(verifiedEvent).to.be.not.undefined; console.log("block verified", verifiedEvent.args.id); + stateVariables = await taikoL1.getStateVariables(); + console.log( + "latest verified block id after verification", + stateVariables[8].toNumber() + ); + + const proverTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(prover); + + expect( + proverTkoBalanceAfterVerification.gt( + proverTkoBalanceBeforeVerification + ) + ).to.be.eq(true); + const newProofReward = await taikoL1.getProofReward( block.proposedAt, block.provenAt diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts index a5abae30f7..8b2140cb4c 100644 --- a/packages/protocol/test/utils/verify.ts +++ b/packages/protocol/test/utils/verify.ts @@ -1,8 +1,8 @@ import { TaikoL1 } from "../../typechain"; import { BlockVerifiedEvent } from "../../typechain/LibVerifying"; -async function verifyBlocks(taikoL1: TaikoL1, num: number) { - const verifyTx = await taikoL1.verifyBlocks(1); +async function verifyBlocks(taikoL1: TaikoL1, maxBlocks: number) { + const verifyTx = await taikoL1.verifyBlocks(maxBlocks); const verifyReceipt = await verifyTx.wait(1); const verifiedEvent: BlockVerifiedEvent = ( verifyReceipt.events as any[] From 197ac8ef2c39714ed9e51c5e8ee7f1a44724b91d Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 20 Jan 2023 22:31:40 -0800 Subject: [PATCH 04/45] test to make sure _rewardProvers issues 1 wei when owner has no tko balance --- .../test/tokenomics/Tokenomics.test.ts | 140 ++++++++++++++++-- packages/protocol/test/tokenomics/utils.ts | 8 + 2 files changed, 139 insertions(+), 9 deletions(-) diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 26c6e17876..78092b7356 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -20,7 +20,7 @@ import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import verifyBlocks from "../utils/verify"; -import { onNewL2Block, sendTinyEtherToZeroAddress } from "./utils"; +import { BlockInfo, onNewL2Block, sendTinyEtherToZeroAddress } from "./utils"; describe("tokenomics", function () { let taikoL1: TaikoL1; @@ -216,7 +216,7 @@ describe("tokenomics", function () { } }); - it.only(`propose blocks, wait til maxNumBlocks is filled. + it(`propose blocks, wait til maxNumBlocks is filled. proverReward should decline should increase as blocks are proved then verified. the provers TKO balance should increase as the blocks are verified and they receive the proofReward.`, async function () { @@ -231,6 +231,13 @@ describe("tokenomics", function () { 0 ); + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proposerSigner.getAddress(), + ethers.utils.parseEther("100") + ); + const prover = new Prover( taikoL1, taikoL2, @@ -273,13 +280,6 @@ describe("tokenomics", function () { let blocksProved: number = 0; - type BlockInfo = { - proposedAt: number; - provenAt: number; - id: number; - parentHash: string; - }; - const blockInfo: BlockInfo[] = []; taikoL1.on( @@ -408,4 +408,126 @@ describe("tokenomics", function () { lastProofReward = newProofReward; } }); + + it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { + const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); + const blockIdsToNumber: any = {}; + + const proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + commitConfirmations.toNumber(), + maxNumBlocks.toNumber(), + 0 + ); + + const prover = new Prover( + taikoL1, + taikoL2, + l1Provider, + l2Provider, + proverSigner + ); + + let hasFailedAssertions: boolean = false; + let blocksProposed: number = 0; + + l2Provider.on("block", async (blockNumber) => { + if (blockNumber <= genesisHeight) return; + if (blocksProposed === 1) { + console.log("max blocks proposed!"); + l2Provider.off("block"); + return; + } + + try { + await expect( + onNewL2Block( + l2Provider, + blockNumber, + proposer, + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ) + ).not.to.throw; + blocksProposed++; + } catch (e) { + hasFailedAssertions = true; + console.error(e); + throw e; + } + }); + + let blockInfo!: BlockInfo; + + taikoL1.on( + "BlockProposed", + async (id: BigNumber, meta: BlockMetadata) => { + /* eslint-disable-next-line */ + while (blocksProposed < 1) { + await sleep(3 * 1000); + } + try { + const event: BlockProvenEvent = await prover.prove( + await proverSigner.getAddress(), + id.toNumber(), + blockIdsToNumber[id.toString()], + meta + ); + + const proposedBlock = await taikoL1.getProposedBlock(id); + + blockInfo = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: event.args.provenAt.toNumber(), + id: event.args.id.toNumber(), + parentHash: event.args.parentHash, + }; + } catch (e) { + hasFailedAssertions = true; + console.error("proving error", e); + throw e; + } + } + ); + + expect(hasFailedAssertions).to.be.eq(false); + + // wait for all blocks to be proven + + /* eslint-disable-next-line */ + while (!blockInfo) { + await sleep(1 * 1000); + } + // now try to verify the blocks and make sure the proof reward shrinks as slots + // free up + while ( + !(await taikoL1.isBlockVerifiable( + blockInfo.id, + blockInfo.parentHash + )) + ) { + await sleep(2 * 1000); + } + + const forkChoice = await taikoL1.getForkChoice( + blockInfo.id, + blockInfo.parentHash + ); + const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + forkChoice.provers[0] + ); + expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); + await verifyBlocks(taikoL1, 1); + + const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( + forkChoice.provers[0] + ); + + // prover should have given given 1 TKO token, since they + // held no TKO balance. + expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); + }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 9d55c788ae..f3b4addb73 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -2,6 +2,13 @@ import { BigNumber, ethers } from "ethers"; import { TaikoL1, TkoToken } from "../../typechain"; import Proposer from "../utils/proposer"; +type BlockInfo = { + proposedAt: number; + provenAt: number; + id: number; + parentHash: string; +}; + async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, blockNumber: number, @@ -51,3 +58,4 @@ const sendTinyEtherToZeroAddress = async (signer: any) => { }; export { sendTinyEtherToZeroAddress, onNewL2Block }; +export type { BlockInfo }; From f64d08fd31c44205a536e42b863b56da365780a7 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 20 Jan 2023 22:32:32 -0800 Subject: [PATCH 05/45] send TKO to the prover for tests --- packages/protocol/test/tokenomics/Tokenomics.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 78092b7356..b96ff9293d 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -231,10 +231,11 @@ describe("tokenomics", function () { 0 ); + // prover needs TKO or their reward will be cut down to 1 wei. await tkoTokenL1 .connect(l1Signer) .mintAnyone( - await proposerSigner.getAddress(), + await proverSigner.getAddress(), ethers.utils.parseEther("100") ); From b9e9176b4e2c52073cf0903962df6db71dfd4e8e Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 23 Jan 2023 22:42:18 -0800 Subject: [PATCH 06/45] wip --- packages/protocol/contracts/L1/TaikoL1.sol | 1 + .../protocol/contracts/L1/libs/LibProving.sol | 14 +- .../test/L1/TestTaikoL1EnableTokenomics.sol | 2 +- .../test/tokenomics/Tokenomics.test.ts | 143 +++++++++--------- packages/protocol/test/tokenomics/utils.ts | 1 + packages/protocol/test/utils/prove.ts | 26 ++-- 6 files changed, 95 insertions(+), 92 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 69bb9e34cb..c2835f5e2b 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -276,6 +276,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { return LibAnchorSignature.signTransaction(hash, k); } + // TODO: we can replace this with getForkChoice I think? function getBlockProvers( uint256 id, bytes32 parentHash diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index bd603ca962..c3a27b3fd7 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -235,12 +235,14 @@ library LibProving { require(evidence.meta.id == target.id, "L1:height"); require(evidence.prover != address(0), "L1:prover"); - _checkMetadata({state: state, config: config, meta: target}); - _validateHeaderForMetadata({ - config: config, - header: evidence.header, - meta: evidence.meta - }); + if (config.enableProofValidation) { + _checkMetadata({state: state, config: config, meta: target}); + _validateHeaderForMetadata({ + config: config, + header: evidence.header, + meta: evidence.meta + }); + } bytes32 blockHash = evidence.header.hashBlockHeader(); diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index 4a573f3308..45aa4c7342 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -23,7 +23,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 config.zkProofsPerBlock = 1; - config.maxVerificationsPerTx = 2; + config.maxVerificationsPerTx = 1; config.commitConfirmations = 1; config.maxProofsPerForkChoice = 5; config.blockMaxGasLimit = 30000000; // TODO diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index b96ff9293d..171deedc3b 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -133,6 +133,24 @@ describe("tokenomics", function () { expect(blockFee.eq(0)).to.be.eq(true); }); + it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { + const { bootstrapDiscountHalvingPeriod } = await taikoL1.getConfig(); + + const iterations: number = 5; + const period: number = bootstrapDiscountHalvingPeriod + .mul(1000) + .toNumber(); + + let lastBlockFee: BigNumber = await taikoL1.getBlockFee(); + + for (let i = 0; i < iterations; i++) { + await sleep(period); + const blockFee = await taikoL1.getBlockFee(); + expect(blockFee.gt(lastBlockFee)).to.be.eq(true); + lastBlockFee = blockFee; + } + }); + it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since slots are growing and no proofs have been submitted", async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); // wait for one period of halving to occur, so fee is not 0. @@ -164,7 +182,7 @@ describe("tokenomics", function () { let hasFailedAssertions: boolean = false; // every time a l2 block is created, we should try to propose it on L1. - l2Provider.on("block", async (blockNumber) => { + l2Provider.on("block", async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; try { const { newProposerTkoBalance, newBlockFee, newProofReward } = @@ -195,28 +213,11 @@ describe("tokenomics", function () { }); await sleep(20 * 1000); + l2Provider.off("block"); expect(hasFailedAssertions).to.be.eq(false); }); - it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { - const { bootstrapDiscountHalvingPeriod } = await taikoL1.getConfig(); - - const iterations: number = 5; - const period: number = bootstrapDiscountHalvingPeriod - .mul(1000) - .toNumber(); - - let lastBlockFee: BigNumber = await taikoL1.getBlockFee(); - - for (let i = 0; i < iterations; i++) { - await sleep(period); - const blockFee = await taikoL1.getBlockFee(); - expect(blockFee.gt(lastBlockFee)).to.be.eq(true); - lastBlockFee = blockFee; - } - }); - - it(`propose blocks, wait til maxNumBlocks is filled. + it.only(`propose blocks, wait til maxNumBlocks is filled. proverReward should decline should increase as blocks are proved then verified. the provers TKO balance should increase as the blocks are verified and they receive the proofReward.`, async function () { @@ -254,7 +255,7 @@ describe("tokenomics", function () { if (blockNumber <= genesisHeight) return; // fill up all slots. if (blocksProposed === maxNumBlocks.toNumber()) { - console.log("max blocks proposed!"); + console.log("max blocks proposed:", maxNumBlocks.toNumber()); l2Provider.off("block"); return; } @@ -274,6 +275,7 @@ describe("tokenomics", function () { blocksProposed++; } catch (e) { hasFailedAssertions = true; + l2Provider.off("block"); console.error(e); throw e; } @@ -294,35 +296,26 @@ describe("tokenomics", function () { while (blocksProposed < maxNumBlocks.toNumber()) { await sleep(3 * 1000); } - console.log("ready to prove"); - try { - const event: BlockProvenEvent = await prover.prove( - await proverSigner.getAddress(), - id.toNumber(), - blockIdsToNumber[id.toString()], - meta - ); - const delay = await taikoL1.getUncleProofDelay( - event.args.id - ); + try { + const blockProvenEvent: BlockProvenEvent = + await prover.prove( + await proverSigner.getAddress(), + id.toNumber(), + blockIdsToNumber[id.toString()], + meta + ); - console.log( - "block proven", - event.args.id, - "parent hash", - event.args.parentHash, - "delay:", - delay.toNumber() - ); + console.log("blockProvenEvent", blockProvenEvent.args); const proposedBlock = await taikoL1.getProposedBlock(id); blockInfo.push({ proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: event.args.provenAt.toNumber(), - id: event.args.id.toNumber(), - parentHash: event.args.parentHash, + provenAt: blockProvenEvent.args.provenAt.toNumber(), + id: blockProvenEvent.args.id.toNumber(), + parentHash: blockProvenEvent.args.parentHash, + blockHash: blockProvenEvent.args.blockHash, }); blocksProved++; } catch (e) { @@ -338,56 +331,61 @@ describe("tokenomics", function () { // wait for all blocks to be proven /* eslint-disable-next-line */ while (blocksProved < maxNumBlocks.toNumber() - 1) { - await sleep(1 * 1000); + await sleep(3 * 1000); } + console.log("ready to verify"); let lastProofReward: BigNumber = BigNumber.from(0); // now try to verify the blocks and make sure the proof reward shrinks as slots // free up for (let i = 1; i < blockInfo.length + 1; i++) { - await sleep(30 * 1000); - console.log("verifying block", i); const block = blockInfo.find((b) => b.id === i) as any as BlockInfo; expect(block).not.to.be.undefined; + console.log("verifying block", block); - const isVerifiable = await taikoL1.isBlockVerifiable( + const forkChoice = await taikoL1.getForkChoice( block.id, block.parentHash ); - console.log("block id ", block.id, "isVerifiable:", isVerifiable); - expect(isVerifiable).to.be.eq(true); - // verify blocks 1 by 1 - const latestL2hash = await taikoL1.getLatestSyncedHeader(); - console.log("latest synced header", latestL2hash); + console.log("fork choice for block id", block.id, ":", forkChoice); - const forkChoice = await taikoL1.getForkChoice( + expect(forkChoice.provers).not.to.be.empty; + expect(forkChoice.blockHash).to.be.eq(block.blockHash); + + let isVerifiable = await taikoL1.isBlockVerifiable( block.id, block.parentHash ); + // make sure block is verifiable and uncle proof delay has passed. + // TODO: sleep for difference between time.Now and fc.provenAt + uncleProofDelay + while (!isVerifiable) { + console.log("block not verifiable", block.id, block.parentHash); + await sleep(3 * 1000); + isVerifiable = await taikoL1.isBlockVerifiable( + block.id, + block.parentHash + ); + } + expect(isVerifiable).to.be.eq(true); + // dont verify first blocks parent hash, because we arent "real L2" in these + // tests, the parent hash will be wrong. + if (i > 1) { + const latestHash = await taikoL1.getLatestSyncedHeader(); + expect(latestHash).to.be.eq(block.parentHash); + } const prover = forkChoice.provers[0]; const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf(prover); - let stateVariables = await taikoL1.getStateVariables(); - console.log( - "latest verified block id", - stateVariables[8].toNumber() - ); - - const verifiedEvent = await verifyBlocks(taikoL1, 5); + const verifiedEvent = await verifyBlocks(taikoL1, 1); expect(verifiedEvent).to.be.not.undefined; - console.log("block verified", verifiedEvent.args.id); - - stateVariables = await taikoL1.getStateVariables(); - console.log( - "latest verified block id after verification", - stateVariables[8].toNumber() - ); + expect(verifiedEvent.args.blockHash).to.be.eq(block.blockHash); + expect(verifiedEvent.args.id.eq(block.id)).to.be.eq(true); const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf(prover); @@ -407,6 +405,7 @@ describe("tokenomics", function () { expect(newProofReward).to.be.lt(lastProofReward); } lastProofReward = newProofReward; + console.log("block ", block.id, "verified!"); } }); @@ -436,7 +435,7 @@ describe("tokenomics", function () { l2Provider.on("block", async (blockNumber) => { if (blockNumber <= genesisHeight) return; if (blocksProposed === 1) { - console.log("max blocks proposed!"); + console.log("max blocks proposed: 1"); l2Provider.off("block"); return; } @@ -456,6 +455,7 @@ describe("tokenomics", function () { blocksProposed++; } catch (e) { hasFailedAssertions = true; + l2Provider.off("block"); console.error(e); throw e; } @@ -466,6 +466,7 @@ describe("tokenomics", function () { taikoL1.on( "BlockProposed", async (id: BigNumber, meta: BlockMetadata) => { + if (hasFailedAssertions) return; /* eslint-disable-next-line */ while (blocksProposed < 1) { await sleep(3 * 1000); @@ -485,7 +486,9 @@ describe("tokenomics", function () { provenAt: event.args.provenAt.toNumber(), id: event.args.id.toNumber(), parentHash: event.args.parentHash, + blockHash: event.args.blockHash, }; + console.log("set block info", blockInfo); } catch (e) { hasFailedAssertions = true; console.error("proving error", e); @@ -502,8 +505,8 @@ describe("tokenomics", function () { while (!blockInfo) { await sleep(1 * 1000); } - // now try to verify the blocks and make sure the proof reward shrinks as slots - // free up + + // make sure block is verifiable before we processe while ( !(await taikoL1.isBlockVerifiable( blockInfo.id, diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index f3b4addb73..0f30e67bd1 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -7,6 +7,7 @@ type BlockInfo = { provenAt: number; id: number; parentHash: string; + blockHash: string; }; async function onNewL2Block( diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index f289e3de60..bedfeb040a 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -48,21 +48,17 @@ const proveBlock = async ( ): Promise => { const config = await taikoL1.getConfig(); const header = await getBlockHeader(l2Provider, blockNumber); - header.blockHeader.difficulty = 0; - header.blockHeader.gasLimit = config.anchorTxGasLimit - .add(header.blockHeader.gasLimit) - .toNumber(); - header.blockHeader.timestamp = meta.timestamp; - // cant prove non-0 blocks - if (header.blockHeader.gasUsed <= 0) { - header.blockHeader.gasUsed = 1; - } - header.blockHeader.mixHash = meta.mixHash; - header.blockHeader.extraData = meta.extraData; - console.log( - "proving block header parent hash", - header.blockHeader.parentHash - ); + // header.blockHeader.difficulty = 0; + // header.blockHeader.gasLimit = config.anchorTxGasLimit + // .add(header.blockHeader.gasLimit) + // .toNumber(); + // header.blockHeader.timestamp = meta.timestamp; + // // cant prove non-0 blocks + // if (header.blockHeader.gasUsed <= 0) { + // header.blockHeader.gasUsed = 1; + // } + // header.blockHeader.mixHash = meta.mixHash; + // header.blockHeader.extraData = meta.extraData; const inputs = buildProveBlockInputs( meta, From 098df430a9d1f33174eb01eb693b5d61603f875a Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 23 Jan 2023 23:54:46 -0800 Subject: [PATCH 07/45] tests pass. need refactor + addtl coverage but commit, propose, prove, verify works, + tokenomcis enabled --- packages/protocol/contracts/L1/TaikoL1.sol | 7 +- .../contracts/L1/libs/LibVerifying.sol | 2 +- .../test/L1/TestTaikoL1EnableTokenomics.sol | 2 +- .../test/tokenomics/Tokenomics.test.ts | 130 ++++++++++-------- packages/protocol/test/tokenomics/utils.ts | 9 +- .../protocol/test/utils/addressManager.ts | 2 +- packages/protocol/test/utils/propose.ts | 1 - packages/protocol/test/utils/prove.ts | 17 +-- packages/protocol/test/utils/prover.ts | 2 - packages/protocol/test/utils/taikoL1.ts | 12 +- 10 files changed, 94 insertions(+), 90 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index c2835f5e2b..e1715e65c3 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -89,7 +89,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { state: state, config: getConfig(), resolver: AddressResolver(this), - maxBlocks: config.maxVerificationsPerTx, + maxBlocks: config.maxVerificationsPerTx + 1, checkHalt: false }); } @@ -247,7 +247,10 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { } function getLatestSyncedHeader() public view override returns (bytes32) { - return state.getL2BlockHash(state.latestVerifiedHeight); + return + state.getL2BlockHash( + state.latestVerifiedHeight % getConfig().blockHashHistory + ); } function getStateVariables() diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 7c4ce65798..726bd3cbcb 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -62,7 +62,7 @@ library LibVerifying { for ( uint256 i = state.latestVerifiedId + 1; - i < state.nextBlockId && processed <= maxBlocks; + i < state.nextBlockId && processed < maxBlocks; i++ ) { TaikoData.ForkChoice storage fc = state.forkChoices[i][ diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index 45aa4c7342..997d1d8744 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -19,7 +19,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { config.chainId = 167; // up to 2048 pending blocks config.maxNumBlocks = 6; - config.blockHashHistory = 3; + config.blockHashHistory = 10; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 config.zkProofsPerBlock = 1; diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 171deedc3b..b8dd27e959 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -1,7 +1,12 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import { ethers as hardhatEthers } from "hardhat"; -import { ConfigManager, TaikoL1, TaikoL2 } from "../../typechain"; +import { + AddressManager, + ConfigManager, + TaikoL1, + TaikoL2, +} from "../../typechain"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import deployAddressManager from "../utils/addressManager"; @@ -34,6 +39,8 @@ describe("tokenomics", function () { let genesisHeight: number; let genesisHash: string; let tkoTokenL1: TestTkoToken; + let l1AddressManager: AddressManager; + let interval: any; beforeEach(async () => { l1Provider = getL1Provider(); @@ -53,7 +60,7 @@ describe("tokenomics", function () { genesisHash = taikoL2.deployTransaction.blockHash as string; genesisHeight = taikoL2.deployTransaction.blockNumber as number; - const l1AddressManager = await deployAddressManager(l1Signer); + l1AddressManager = await deployAddressManager(l1Signer); taikoL1 = await deployTaikoL1( l1AddressManager, genesisHash, @@ -73,41 +80,50 @@ describe("tokenomics", function () { taikoL1.address ); - await l1AddressManager.setAddress( - `${chainId}.tko_token`, - tkoTokenL1.address - ); + await ( + await l1AddressManager.setAddress( + `${chainId}.tko_token`, + tkoTokenL1.address + ) + ).wait(1); const { chainId: l2ChainId } = await l2Provider.getNetwork(); - await l1AddressManager.setAddress( - `${l2ChainId}.taiko`, - taikoL2.address - ); + await ( + await l1AddressManager.setAddress( + `${l2ChainId}.taiko`, + taikoL2.address + ) + ).wait(1); - await l1AddressManager.setAddress( - `${chainId}.proof_verifier`, - taikoL1.address - ); + await ( + await l1AddressManager.setAddress( + `${chainId}.proof_verifier`, + taikoL1.address + ) + ).wait(1); const configManager: ConfigManager = await ( await hardhatEthers.getContractFactory("ConfigManager") ) .connect(l1Signer) .deploy(); + await configManager.deployed(); await l1AddressManager.setAddress( `${chainId}.config_manager`, configManager.address ); - await tkoTokenL1 + const mintTx = await tkoTokenL1 .connect(l1Signer) .mintAnyone( await proposerSigner.getAddress(), ethers.utils.parseEther("100") ); + await mintTx.wait(1); + expect( await tkoTokenL1.balanceOf(await proposerSigner.getAddress()) ).to.be.eq(ethers.utils.parseEther("100")); @@ -116,7 +132,7 @@ describe("tokenomics", function () { await l2Provider.send("evm_setAutomine", [true]); // send transactions to L1 so we always get new blocks - setInterval( + interval = setInterval( async () => await sendTinyEtherToZeroAddress(l1Signer), 1 * 1000 ); @@ -128,8 +144,12 @@ describe("tokenomics", function () { await tx.wait(1); }); + afterEach(() => clearInterval(interval)); + it("expects the blockFee to go be 0 when no periods have passed", async function () { - const blockFee = await taikoL1.getBlockFee(); + // deploy a new instance of TaikoL1 so no blocks have passed. + const tL1 = await deployTaikoL1(l1AddressManager, genesisHash, true); + const blockFee = await tL1.getBlockFee(); expect(blockFee.eq(0)).to.be.eq(true); }); @@ -217,7 +237,7 @@ describe("tokenomics", function () { expect(hasFailedAssertions).to.be.eq(false); }); - it.only(`propose blocks, wait til maxNumBlocks is filled. + it(`propose blocks, wait til maxNumBlocks is filled. proverReward should decline should increase as blocks are proved then verified. the provers TKO balance should increase as the blocks are verified and they receive the proofReward.`, async function () { @@ -255,7 +275,6 @@ describe("tokenomics", function () { if (blockNumber <= genesisHeight) return; // fill up all slots. if (blocksProposed === maxNumBlocks.toNumber()) { - console.log("max blocks proposed:", maxNumBlocks.toNumber()); l2Provider.off("block"); return; } @@ -288,8 +307,6 @@ describe("tokenomics", function () { taikoL1.on( "BlockProposed", async (id: BigNumber, meta: BlockMetadata) => { - console.log("proving block: id", id.toString()); - // wait until we fill up all slots, so we can // then prove blocks in order, and each time a block is proven then verified, // we can expect the proofReward to go down as slots become available. @@ -298,6 +315,7 @@ describe("tokenomics", function () { } try { + const proverAddress = await proverSigner.getAddress(); const blockProvenEvent: BlockProvenEvent = await prover.prove( await proverSigner.getAddress(), @@ -306,16 +324,33 @@ describe("tokenomics", function () { meta ); - console.log("blockProvenEvent", blockProvenEvent.args); - const proposedBlock = await taikoL1.getProposedBlock(id); + const forkChoice = await taikoL1.getForkChoice( + blockProvenEvent.args.id.toNumber(), + blockProvenEvent.args.parentHash + ); + + expect(forkChoice.blockHash).to.be.eq( + blockProvenEvent.args.blockHash + ); + + expect(forkChoice.provers[0]).to.be.eq(proverAddress); + + await sleep(5 * 1000); + const isVerifiable = await taikoL1.isBlockVerifiable( + blockProvenEvent.args.id.toNumber(), + blockProvenEvent.args.parentHash + ); + + expect(isVerifiable).to.be.eq(true); blockInfo.push({ proposedAt: proposedBlock.proposedAt.toNumber(), provenAt: blockProvenEvent.args.provenAt.toNumber(), id: blockProvenEvent.args.id.toNumber(), parentHash: blockProvenEvent.args.parentHash, blockHash: blockProvenEvent.args.blockHash, + forkChoice: forkChoice, }); blocksProved++; } catch (e) { @@ -344,39 +379,13 @@ describe("tokenomics", function () { expect(block).not.to.be.undefined; console.log("verifying block", block); - const forkChoice = await taikoL1.getForkChoice( - block.id, - block.parentHash - ); - - console.log("fork choice for block id", block.id, ":", forkChoice); - - expect(forkChoice.provers).not.to.be.empty; - expect(forkChoice.blockHash).to.be.eq(block.blockHash); - - let isVerifiable = await taikoL1.isBlockVerifiable( - block.id, - block.parentHash - ); - // make sure block is verifiable and uncle proof delay has passed. - // TODO: sleep for difference between time.Now and fc.provenAt + uncleProofDelay - while (!isVerifiable) { - console.log("block not verifiable", block.id, block.parentHash); - await sleep(3 * 1000); - isVerifiable = await taikoL1.isBlockVerifiable( - block.id, - block.parentHash - ); - } - expect(isVerifiable).to.be.eq(true); - // dont verify first blocks parent hash, because we arent "real L2" in these // tests, the parent hash will be wrong. if (i > 1) { const latestHash = await taikoL1.getLatestSyncedHeader(); expect(latestHash).to.be.eq(block.parentHash); } - const prover = forkChoice.provers[0]; + const prover = block.forkChoice.provers[0]; const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf(prover); @@ -405,6 +414,9 @@ describe("tokenomics", function () { expect(newProofReward).to.be.lt(lastProofReward); } lastProofReward = newProofReward; + + const latestHash = await taikoL1.getLatestSyncedHeader(); + expect(latestHash).to.be.eq(block.blockHash); console.log("block ", block.id, "verified!"); } }); @@ -435,7 +447,6 @@ describe("tokenomics", function () { l2Provider.on("block", async (blockNumber) => { if (blockNumber <= genesisHeight) return; if (blocksProposed === 1) { - console.log("max blocks proposed: 1"); l2Provider.off("block"); return; } @@ -481,14 +492,19 @@ describe("tokenomics", function () { const proposedBlock = await taikoL1.getProposedBlock(id); + const forkChoice = await taikoL1.getForkChoice( + blockInfo.id, + blockInfo.parentHash + ); + blockInfo = { proposedAt: proposedBlock.proposedAt.toNumber(), provenAt: event.args.provenAt.toNumber(), id: event.args.id.toNumber(), parentHash: event.args.parentHash, blockHash: event.args.blockHash, + forkChoice: forkChoice, }; - console.log("set block info", blockInfo); } catch (e) { hasFailedAssertions = true; console.error("proving error", e); @@ -499,7 +515,7 @@ describe("tokenomics", function () { expect(hasFailedAssertions).to.be.eq(false); - // wait for all blocks to be proven + // wait for block to be proven asynchronously. /* eslint-disable-next-line */ while (!blockInfo) { @@ -516,18 +532,14 @@ describe("tokenomics", function () { await sleep(2 * 1000); } - const forkChoice = await taikoL1.getForkChoice( - blockInfo.id, - blockInfo.parentHash - ); const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( - forkChoice.provers[0] + blockInfo.forkChoice.provers[0] ); expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); await verifyBlocks(taikoL1, 1); const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( - forkChoice.provers[0] + blockInfo.forkChoice.provers[0] ); // prover should have given given 1 TKO token, since they diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 0f30e67bd1..b63091ca11 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -2,12 +2,19 @@ import { BigNumber, ethers } from "ethers"; import { TaikoL1, TkoToken } from "../../typechain"; import Proposer from "../utils/proposer"; +type ForkChoice = { + provenAt: BigNumber; + provers: string[]; + blockHash: string; +}; + type BlockInfo = { proposedAt: number; provenAt: number; id: number; parentHash: string; blockHash: string; + forkChoice: ForkChoice; }; async function onNewL2Block( @@ -31,8 +38,6 @@ async function onNewL2Block( const { id, meta } = (proposedEvent as any).args; - console.log("-----------PROPOSED---------------", block.number, id); - blockIdsToNumber[id.toString()] = block.number; const newProofReward = await taikoL1.getProofReward( diff --git a/packages/protocol/test/utils/addressManager.ts b/packages/protocol/test/utils/addressManager.ts index 47e577c116..0b0ad048c8 100644 --- a/packages/protocol/test/utils/addressManager.ts +++ b/packages/protocol/test/utils/addressManager.ts @@ -8,7 +8,7 @@ const deployAddressManager = async (signer: ethers.Signer) => { ) .connect(signer) .deploy(); - await addressManager.init(); + await (await addressManager.init()).wait(1); return addressManager; }; diff --git a/packages/protocol/test/utils/propose.ts b/packages/protocol/test/utils/propose.ts index 7d961c0ae5..f5655ccf54 100644 --- a/packages/protocol/test/utils/propose.ts +++ b/packages/protocol/test/utils/propose.ts @@ -40,7 +40,6 @@ const proposeBlock = async ( const inputs = buildProposeBlockInputs(block, meta); const tx = await taikoL1.proposeBlock(inputs); - console.log("Proposed block", tx.hash); const receipt = await tx.wait(1); return receipt; }; diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index bedfeb040a..db4e3df350 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { TaikoL1, TaikoL2 } from "../../typechain"; +import { TaikoL1 } from "../../typechain"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { encodeEvidence } from "./encoding"; @@ -35,11 +35,8 @@ const buildProveBlockInputs = ( return inputs; }; -// TODO const proveBlock = async ( taikoL1: TaikoL1, - taikoL2: TaikoL2, - l2Signer: ethers.Signer, l2Provider: ethers.providers.JsonRpcProvider, proverAddress: string, blockId: number, @@ -48,18 +45,6 @@ const proveBlock = async ( ): Promise => { const config = await taikoL1.getConfig(); const header = await getBlockHeader(l2Provider, blockNumber); - // header.blockHeader.difficulty = 0; - // header.blockHeader.gasLimit = config.anchorTxGasLimit - // .add(header.blockHeader.gasLimit) - // .toNumber(); - // header.blockHeader.timestamp = meta.timestamp; - // // cant prove non-0 blocks - // if (header.blockHeader.gasUsed <= 0) { - // header.blockHeader.gasUsed = 1; - // } - // header.blockHeader.mixHash = meta.mixHash; - // header.blockHeader.extraData = meta.extraData; - const inputs = buildProveBlockInputs( meta, header.blockHeader, diff --git a/packages/protocol/test/utils/prover.ts b/packages/protocol/test/utils/prover.ts index 35e0626a30..373fc94bde 100644 --- a/packages/protocol/test/utils/prover.ts +++ b/packages/protocol/test/utils/prover.ts @@ -42,8 +42,6 @@ class Prover { try { blockProvenEvent = await proveBlock( this.taikoL1, - this.taikoL2, - this.l2Signer, this.l2Provider, proverAddress, blockId, diff --git a/packages/protocol/test/utils/taikoL1.ts b/packages/protocol/test/utils/taikoL1.ts index f6191c61a5..d49be70774 100644 --- a/packages/protocol/test/utils/taikoL1.ts +++ b/packages/protocol/test/utils/taikoL1.ts @@ -48,11 +48,13 @@ async function deployTaikoL1( ) ).deploy(); - await taikoL1.init( - addressManager.address, - genesisHash, - feeBase ?? defaultFeeBase - ); + await ( + await taikoL1.init( + addressManager.address, + genesisHash, + feeBase ?? defaultFeeBase + ) + ).wait(1); return taikoL1 as TaikoL1; } From 61f5efb466a0f5d26321bf8cda34ad8700139c23 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 00:06:52 -0800 Subject: [PATCH 08/45] rm getblockprovers --- packages/protocol/contracts/L1/TaikoL1.sol | 8 -------- packages/protocol/test/L1/TaikoL1.test.ts | 11 ----------- 2 files changed, 19 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index e1715e65c3..06761e5e2f 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -279,14 +279,6 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { return LibAnchorSignature.signTransaction(hash, k); } - // TODO: we can replace this with getForkChoice I think? - function getBlockProvers( - uint256 id, - bytes32 parentHash - ) public view returns (address[] memory) { - return state.forkChoices[id][parentHash].provers; - } - function getForkChoice( uint256 id, bytes32 parentHash diff --git a/packages/protocol/test/L1/TaikoL1.test.ts b/packages/protocol/test/L1/TaikoL1.test.ts index 96d70461eb..270d6d338b 100644 --- a/packages/protocol/test/L1/TaikoL1.test.ts +++ b/packages/protocol/test/L1/TaikoL1.test.ts @@ -36,17 +36,6 @@ describe("TaikoL1", function () { }); }); - describe("getBlockProvers()", async function () { - it("should return empty list when there is no proof for that block", async function () { - const provers = await taikoL1.getBlockProvers( - Math.ceil(Math.random() * 1024), - randomBytes32() - ); - - expect(provers).to.be.empty; - }); - }); - describe("halt()", async function () { it("should revert called by nonOwner", async function () { const initiallyHalted = await taikoL1.isHalted(); From e929e809f7cb7ca2b0384f35b5150cc70a0d239b Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 00:40:38 -0800 Subject: [PATCH 09/45] add sleep delay utils method, rename some variables --- .../test/tokenomics/Tokenomics.test.ts | 76 +++++++++++-------- packages/protocol/test/tokenomics/utils.ts | 18 ++++- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index b8dd27e959..300d2897ab 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -25,7 +25,12 @@ import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import verifyBlocks from "../utils/verify"; -import { BlockInfo, onNewL2Block, sendTinyEtherToZeroAddress } from "./utils"; +import { + BlockInfo, + onNewL2Block, + sendTinyEtherToZeroAddress, + sleepUntilBlockIsVerifiable, +} from "./utils"; describe("tokenomics", function () { let taikoL1: TaikoL1; @@ -316,40 +321,47 @@ describe("tokenomics", function () { try { const proverAddress = await proverSigner.getAddress(); - const blockProvenEvent: BlockProvenEvent = - await prover.prove( - await proverSigner.getAddress(), - id.toNumber(), - blockIdsToNumber[id.toString()], - meta - ); + const { args } = await prover.prove( + await proverSigner.getAddress(), + id.toNumber(), + blockIdsToNumber[id.toString()], + meta + ); + const { + blockHash, + id: blockId, + parentHash, + provenAt, + } = args; const proposedBlock = await taikoL1.getProposedBlock(id); const forkChoice = await taikoL1.getForkChoice( - blockProvenEvent.args.id.toNumber(), - blockProvenEvent.args.parentHash + blockId.toNumber(), + parentHash ); - expect(forkChoice.blockHash).to.be.eq( - blockProvenEvent.args.blockHash - ); + expect(forkChoice.blockHash).to.be.eq(blockHash); expect(forkChoice.provers[0]).to.be.eq(proverAddress); - await sleep(5 * 1000); + await sleepUntilBlockIsVerifiable( + taikoL1, + blockId.toNumber(), + provenAt.toNumber() + ); const isVerifiable = await taikoL1.isBlockVerifiable( - blockProvenEvent.args.id.toNumber(), - blockProvenEvent.args.parentHash + id.toNumber(), + parentHash ); expect(isVerifiable).to.be.eq(true); blockInfo.push({ proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: blockProvenEvent.args.provenAt.toNumber(), - id: blockProvenEvent.args.id.toNumber(), - parentHash: blockProvenEvent.args.parentHash, - blockHash: blockProvenEvent.args.blockHash, + provenAt: provenAt.toNumber(), + id: id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, forkChoice: forkChoice, }); blocksProved++; @@ -368,7 +380,6 @@ describe("tokenomics", function () { while (blocksProved < maxNumBlocks.toNumber() - 1) { await sleep(3 * 1000); } - console.log("ready to verify"); let lastProofReward: BigNumber = BigNumber.from(0); @@ -477,7 +488,6 @@ describe("tokenomics", function () { taikoL1.on( "BlockProposed", async (id: BigNumber, meta: BlockMetadata) => { - if (hasFailedAssertions) return; /* eslint-disable-next-line */ while (blocksProposed < 1) { await sleep(3 * 1000); @@ -493,8 +503,8 @@ describe("tokenomics", function () { const proposedBlock = await taikoL1.getProposedBlock(id); const forkChoice = await taikoL1.getForkChoice( - blockInfo.id, - blockInfo.parentHash + id.toNumber(), + event.args.parentHash ); blockInfo = { @@ -523,15 +533,17 @@ describe("tokenomics", function () { } // make sure block is verifiable before we processe - while ( - !(await taikoL1.isBlockVerifiable( - blockInfo.id, - blockInfo.parentHash - )) - ) { - await sleep(2 * 1000); - } + await sleepUntilBlockIsVerifiable( + taikoL1, + blockInfo.id, + blockInfo.provenAt + ); + const isVerifiable = await taikoL1.isBlockVerifiable( + blockInfo.id, + blockInfo.parentHash + ); + expect(isVerifiable).to.be.eq(true); const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( blockInfo.forkChoice.provers[0] ); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index b63091ca11..8bb5eafe57 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -1,6 +1,7 @@ import { BigNumber, ethers } from "ethers"; import { TaikoL1, TkoToken } from "../../typechain"; import Proposer from "../utils/proposer"; +import sleep from "../utils/sleep"; type ForkChoice = { provenAt: BigNumber; @@ -17,6 +18,15 @@ type BlockInfo = { forkChoice: ForkChoice; }; +async function sleepUntilBlockIsVerifiable( + taikoL1: TaikoL1, + id: number, + provenAt: number +) { + const delay = await taikoL1.getUncleProofDelay(id); + const delayInMs = delay.mul(1000); + await sleep(delayInMs.toNumber()); +} async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, blockNumber: number, @@ -63,5 +73,9 @@ const sendTinyEtherToZeroAddress = async (signer: any) => { .wait(1); }; -export { sendTinyEtherToZeroAddress, onNewL2Block }; -export type { BlockInfo }; +export { + sendTinyEtherToZeroAddress, + onNewL2Block, + sleepUntilBlockIsVerifiable, +}; +export type { BlockInfo, ForkChoice }; From 1b6383cf912369510efc5a10c00abe2443aac6b1 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 00:46:24 -0800 Subject: [PATCH 10/45] helpers --- packages/protocol/test/tokenomics/Tokenomics.test.ts | 3 ++- packages/protocol/test/tokenomics/utils.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 300d2897ab..52b1713f95 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -350,8 +350,9 @@ describe("tokenomics", function () { blockId.toNumber(), provenAt.toNumber() ); + const isVerifiable = await taikoL1.isBlockVerifiable( - id.toNumber(), + blockId.toNumber(), parentHash ); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 8bb5eafe57..4a28f2bfb1 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -25,7 +25,7 @@ async function sleepUntilBlockIsVerifiable( ) { const delay = await taikoL1.getUncleProofDelay(id); const delayInMs = delay.mul(1000); - await sleep(delayInMs.toNumber()); + await sleep(5 * delayInMs.toNumber()); } async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, From def8ae0475afb91eff51a75005c367c9cbf3cbfc Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 11:34:56 -0800 Subject: [PATCH 11/45] clean up tests --- .../protocol/test/tokenomics/blockFee.test.ts | 126 +++++ ...Tokenomics.test.ts => proofReward.test.ts} | 461 +++++------------- packages/protocol/test/tokenomics/utils.ts | 125 ++++- 3 files changed, 385 insertions(+), 327 deletions(-) create mode 100644 packages/protocol/test/tokenomics/blockFee.test.ts rename packages/protocol/test/tokenomics/{Tokenomics.test.ts => proofReward.test.ts} (58%) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts new file mode 100644 index 0000000000..24d92b1eac --- /dev/null +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -0,0 +1,126 @@ +import { expect } from "chai"; +import { BigNumber, ethers } from "ethers"; +import { AddressManager, TaikoL1 } from "../../typechain"; +import { TestTkoToken } from "../../typechain/TestTkoToken"; +import Proposer from "../utils/proposer"; + +import sleep from "../utils/sleep"; +import { deployTaikoL1 } from "../utils/taikoL1"; +import { initTokenomicsFixture, onNewL2Block } from "./utils"; + +describe("tokenomics: blockFee", function () { + let taikoL1: TaikoL1; + let l2Provider: ethers.providers.JsonRpcProvider; + let proposerSigner: any; + let genesisHeight: number; + let genesisHash: string; + let tkoTokenL1: TestTkoToken; + let l1AddressManager: AddressManager; + let interval: any; + + beforeEach(async () => { + ({ + taikoL1, + l2Provider, + proposerSigner, + genesisHeight, + genesisHash, + tkoTokenL1, + l1AddressManager, + interval, + } = await initTokenomicsFixture()); + }); + + afterEach(() => clearInterval(interval)); + + it("expects the blockFee to go be 0 when no periods have passed", async function () { + // deploy a new instance of TaikoL1 so no blocks have passed. + const tL1 = await deployTaikoL1(l1AddressManager, genesisHash, true); + const blockFee = await tL1.getBlockFee(); + expect(blockFee.eq(0)).to.be.eq(true); + }); + + it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { + const { bootstrapDiscountHalvingPeriod } = await taikoL1.getConfig(); + + const iterations: number = 5; + const period: number = bootstrapDiscountHalvingPeriod + .mul(1000) + .toNumber(); + + let lastBlockFee: BigNumber = await taikoL1.getBlockFee(); + + for (let i = 0; i < iterations; i++) { + await sleep(period); + const blockFee = await taikoL1.getBlockFee(); + expect(blockFee.gt(lastBlockFee)).to.be.eq(true); + lastBlockFee = blockFee; + } + }); + + it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since slots are growing and no proofs have been submitted", async function () { + const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); + // wait for one period of halving to occur, so fee is not 0. + const blockIdsToNumber: any = {}; + + // set up a proposer to continually propose new blocks + const proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + commitConfirmations.toNumber(), + maxNumBlocks.toNumber(), + 0 + ); + + // get the initiaal tkoBalance, which should decrease every block proposal + let lastProposerTkoBalance = await tkoTokenL1.balanceOf( + await proposerSigner.getAddress() + ); + + // do the same for the blockFee, which should increase every block proposal + // with proofs not being submitted. + let lastBlockFee = await taikoL1.getBlockFee(); + while (lastBlockFee.eq(0)) { + await sleep(500); + lastBlockFee = await taikoL1.getBlockFee(); + } + + let lastProofReward = BigNumber.from(0); + + let hasFailedAssertions: boolean = false; + // every time a l2 block is created, we should try to propose it on L1. + l2Provider.on("block", async (blockNumber: number) => { + if (blockNumber <= genesisHeight) return; + try { + const { newProposerTkoBalance, newBlockFee, newProofReward } = + await onNewL2Block( + l2Provider, + blockNumber, + proposer, + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + + expect( + newProposerTkoBalance.lt(lastProposerTkoBalance) + ).to.be.eq(true); + expect(newBlockFee.gt(lastBlockFee)).to.be.eq(true); + expect(newProofReward.gt(lastProofReward)).to.be.eq(true); + + lastBlockFee = newBlockFee; + lastProofReward = newProofReward; + lastProposerTkoBalance = newProposerTkoBalance; + } catch (e) { + hasFailedAssertions = true; + console.error(e); + throw e; + } + }); + + await sleep(20 * 1000); + l2Provider.off("block"); + expect(hasFailedAssertions).to.be.eq(false); + }); +}); diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts similarity index 58% rename from packages/protocol/test/tokenomics/Tokenomics.test.ts rename to packages/protocol/test/tokenomics/proofReward.test.ts index 52b1713f95..c4bb007528 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,187 +1,55 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; -import { ethers as hardhatEthers } from "hardhat"; -import { - AddressManager, - ConfigManager, - TaikoL1, - TaikoL2, -} from "../../typechain"; +import { TaikoL1, TaikoL2 } from "../../typechain"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; -import deployAddressManager from "../utils/addressManager"; import { BlockMetadata } from "../utils/block_metadata"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; // import Prover from "../utils/prover"; -import { - getDefaultL2Signer, - getL1Provider, - getL2Provider, -} from "../utils/provider"; -import createAndSeedWallets from "../utils/seed"; + import sleep from "../utils/sleep"; -import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; -import { deployTaikoL2 } from "../utils/taikoL2"; -import deployTkoToken from "../utils/tkoToken"; import verifyBlocks from "../utils/verify"; import { BlockInfo, + initTokenomicsFixture, onNewL2Block, - sendTinyEtherToZeroAddress, sleepUntilBlockIsVerifiable, } from "./utils"; -describe("tokenomics", function () { +describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; let taikoL2: TaikoL2; let l1Provider: ethers.providers.JsonRpcProvider; let l2Provider: ethers.providers.JsonRpcProvider; let l1Signer: any; - let l2Signer: any; let proposerSigner: any; let proverSigner: any; let genesisHeight: number; - let genesisHash: string; let tkoTokenL1: TestTkoToken; - let l1AddressManager: AddressManager; let interval: any; beforeEach(async () => { - l1Provider = getL1Provider(); - - l1Provider.pollingInterval = 100; - - const signers = await hardhatEthers.getSigners(); - l1Signer = signers[0]; - - l2Provider = getL2Provider(); - - l2Signer = await getDefaultL2Signer(); - - const l2AddressManager = await deployAddressManager(l2Signer); - taikoL2 = await deployTaikoL2(l2Signer, l2AddressManager, false); - - genesisHash = taikoL2.deployTransaction.blockHash as string; - genesisHeight = taikoL2.deployTransaction.blockNumber as number; - - l1AddressManager = await deployAddressManager(l1Signer); - taikoL1 = await deployTaikoL1( - l1AddressManager, - genesisHash, - true, - defaultFeeBase - ); - const { chainId } = await l1Provider.getNetwork(); - - [proposerSigner, proverSigner] = await createAndSeedWallets( - 2, - l1Signer - ); - - tkoTokenL1 = await deployTkoToken( + ({ + taikoL1, + taikoL2, + l1Provider, + l2Provider, l1Signer, - l1AddressManager, - taikoL1.address - ); - - await ( - await l1AddressManager.setAddress( - `${chainId}.tko_token`, - tkoTokenL1.address - ) - ).wait(1); - - const { chainId: l2ChainId } = await l2Provider.getNetwork(); - - await ( - await l1AddressManager.setAddress( - `${l2ChainId}.taiko`, - taikoL2.address - ) - ).wait(1); - - await ( - await l1AddressManager.setAddress( - `${chainId}.proof_verifier`, - taikoL1.address - ) - ).wait(1); - - const configManager: ConfigManager = await ( - await hardhatEthers.getContractFactory("ConfigManager") - ) - .connect(l1Signer) - .deploy(); - await configManager.deployed(); - - await l1AddressManager.setAddress( - `${chainId}.config_manager`, - configManager.address - ); - - const mintTx = await tkoTokenL1 - .connect(l1Signer) - .mintAnyone( - await proposerSigner.getAddress(), - ethers.utils.parseEther("100") - ); - - await mintTx.wait(1); - - expect( - await tkoTokenL1.balanceOf(await proposerSigner.getAddress()) - ).to.be.eq(ethers.utils.parseEther("100")); - - // 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 - interval = setInterval( - async () => await sendTinyEtherToZeroAddress(l1Signer), - 1 * 1000 - ); - - const tx = await l2Signer.sendTransaction({ - to: proverSigner.address, - value: ethers.utils.parseUnits("1", "ether"), - }); - await tx.wait(1); + proposerSigner, + proverSigner, + genesisHeight, + tkoTokenL1, + interval, + } = await initTokenomicsFixture()); }); afterEach(() => clearInterval(interval)); - it("expects the blockFee to go be 0 when no periods have passed", async function () { - // deploy a new instance of TaikoL1 so no blocks have passed. - const tL1 = await deployTaikoL1(l1AddressManager, genesisHash, true); - const blockFee = await tL1.getBlockFee(); - expect(blockFee.eq(0)).to.be.eq(true); - }); - - it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { - const { bootstrapDiscountHalvingPeriod } = await taikoL1.getConfig(); - - const iterations: number = 5; - const period: number = bootstrapDiscountHalvingPeriod - .mul(1000) - .toNumber(); - - let lastBlockFee: BigNumber = await taikoL1.getBlockFee(); - - for (let i = 0; i < iterations; i++) { - await sleep(period); - const blockFee = await taikoL1.getBlockFee(); - expect(blockFee.gt(lastBlockFee)).to.be.eq(true); - lastBlockFee = blockFee; - } - }); - - it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since slots are growing and no proofs have been submitted", async function () { + it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - // wait for one period of halving to occur, so fee is not 0. const blockIdsToNumber: any = {}; - // set up a proposer to continually propose new blocks const proposer = new Proposer( taikoL1.connect(proposerSigner), l2Provider, @@ -190,28 +58,27 @@ describe("tokenomics", function () { 0 ); - // get the initiaal tkoBalance, which should decrease every block proposal - let lastProposerTkoBalance = await tkoTokenL1.balanceOf( - await proposerSigner.getAddress() + const prover = new Prover( + taikoL1, + taikoL2, + l1Provider, + l2Provider, + proverSigner ); - // do the same for the blockFee, which should increase every block proposal - // with proofs not being submitted. - let lastBlockFee = await taikoL1.getBlockFee(); - while (lastBlockFee.eq(0)) { - await sleep(500); - lastBlockFee = await taikoL1.getBlockFee(); - } - - let lastProofReward = BigNumber.from(0); - let hasFailedAssertions: boolean = false; - // every time a l2 block is created, we should try to propose it on L1. - l2Provider.on("block", async (blockNumber: number) => { + let blocksProposed: number = 0; + + const listener = async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; + if (blocksProposed === 1) { + l2Provider.off("block", listener); + return; + } + try { - const { newProposerTkoBalance, newBlockFee, newProofReward } = - await onNewL2Block( + await expect( + onNewL2Block( l2Provider, blockNumber, proposer, @@ -219,27 +86,93 @@ describe("tokenomics", function () { taikoL1, proposerSigner, tkoTokenL1 - ); - - expect( - newProposerTkoBalance.lt(lastProposerTkoBalance) - ).to.be.eq(true); - expect(newBlockFee.gt(lastBlockFee)).to.be.eq(true); - expect(newProofReward.gt(lastProofReward)).to.be.eq(true); - - lastBlockFee = newBlockFee; - lastProofReward = newProofReward; - lastProposerTkoBalance = newProposerTkoBalance; + ) + ).not.to.throw; + blocksProposed++; } catch (e) { hasFailedAssertions = true; + l2Provider.off("block", listener); console.error(e); throw e; } - }); + }; + + l2Provider.on("block", listener); + + let blockInfo!: BlockInfo; + + taikoL1.on( + "BlockProposed", + async (id: BigNumber, meta: BlockMetadata) => { + /* eslint-disable-next-line */ + while (blocksProposed < 1) { + await sleep(3 * 1000); + } + try { + const event: BlockProvenEvent = await prover.prove( + await proverSigner.getAddress(), + id.toNumber(), + blockIdsToNumber[id.toString()], + meta + ); + + const proposedBlock = await taikoL1.getProposedBlock(id); + + const forkChoice = await taikoL1.getForkChoice( + id.toNumber(), + event.args.parentHash + ); + + blockInfo = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: event.args.provenAt.toNumber(), + id: event.args.id.toNumber(), + parentHash: event.args.parentHash, + blockHash: event.args.blockHash, + forkChoice: forkChoice, + }; + } catch (e) { + hasFailedAssertions = true; + console.error("proving error", e); + throw e; + } + } + ); - await sleep(20 * 1000); - l2Provider.off("block"); expect(hasFailedAssertions).to.be.eq(false); + + // wait for block to be proven asynchronously. + + /* eslint-disable-next-line */ + while (!blockInfo) { + await sleep(1 * 1000); + } + + // make sure block is verifiable before we processe + await sleepUntilBlockIsVerifiable( + taikoL1, + blockInfo.id, + blockInfo.provenAt + ); + + const isVerifiable = await taikoL1.isBlockVerifiable( + blockInfo.id, + blockInfo.parentHash + ); + expect(isVerifiable).to.be.eq(true); + const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + blockInfo.forkChoice.provers[0] + ); + expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); + await verifyBlocks(taikoL1, 1); + + const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( + blockInfo.forkChoice.provers[0] + ); + + // prover should have given given 1 TKO token, since they + // held no TKO balance. + expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); }); it(`propose blocks, wait til maxNumBlocks is filled. @@ -276,11 +209,11 @@ describe("tokenomics", function () { let hasFailedAssertions: boolean = false; let blocksProposed: number = 0; - l2Provider.on("block", async (blockNumber) => { + const listener = async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; // fill up all slots. if (blocksProposed === maxNumBlocks.toNumber()) { - l2Provider.off("block"); + l2Provider.off("block", listener); return; } @@ -299,11 +232,13 @@ describe("tokenomics", function () { blocksProposed++; } catch (e) { hasFailedAssertions = true; - l2Provider.off("block"); + l2Provider.off("block", listener); console.error(e); throw e; } - }); + }; + + l2Provider.on("block", listener); let blocksProved: number = 0; @@ -315,6 +250,8 @@ describe("tokenomics", function () { // wait until we fill up all slots, so we can // then prove blocks in order, and each time a block is proven then verified, // we can expect the proofReward to go down as slots become available. + + console.log("p", id.toNumber()); while (blocksProposed < maxNumBlocks.toNumber()) { await sleep(3 * 1000); } @@ -345,18 +282,6 @@ describe("tokenomics", function () { expect(forkChoice.provers[0]).to.be.eq(proverAddress); - await sleepUntilBlockIsVerifiable( - taikoL1, - blockId.toNumber(), - provenAt.toNumber() - ); - - const isVerifiable = await taikoL1.isBlockVerifiable( - blockId.toNumber(), - parentHash - ); - - expect(isVerifiable).to.be.eq(true); blockInfo.push({ proposedAt: proposedBlock.proposedAt.toNumber(), provenAt: provenAt.toNumber(), @@ -374,14 +299,14 @@ describe("tokenomics", function () { } ); - expect(hasFailedAssertions).to.be.eq(false); - // wait for all blocks to be proven /* eslint-disable-next-line */ while (blocksProved < maxNumBlocks.toNumber() - 1) { await sleep(3 * 1000); } + expect(hasFailedAssertions).to.be.eq(false); + let lastProofReward: BigNumber = BigNumber.from(0); // now try to verify the blocks and make sure the proof reward shrinks as slots @@ -389,7 +314,19 @@ describe("tokenomics", function () { for (let i = 1; i < blockInfo.length + 1; i++) { const block = blockInfo.find((b) => b.id === i) as any as BlockInfo; expect(block).not.to.be.undefined; - console.log("verifying block", block); + + await sleepUntilBlockIsVerifiable( + taikoL1, + block.id, + block.provenAt + ); + + const isVerifiable = await taikoL1.isBlockVerifiable( + block.id, + block.parentHash + ); + + expect(isVerifiable).to.be.eq(true); // dont verify first blocks parent hash, because we arent "real L2" in these // tests, the parent hash will be wrong. @@ -429,134 +366,6 @@ describe("tokenomics", function () { const latestHash = await taikoL1.getLatestSyncedHeader(); expect(latestHash).to.be.eq(block.blockHash); - console.log("block ", block.id, "verified!"); } }); - - it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { - const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const blockIdsToNumber: any = {}; - - const proposer = new Proposer( - taikoL1.connect(proposerSigner), - l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), - 0 - ); - - const prover = new Prover( - taikoL1, - taikoL2, - l1Provider, - l2Provider, - proverSigner - ); - - let hasFailedAssertions: boolean = false; - let blocksProposed: number = 0; - - l2Provider.on("block", async (blockNumber) => { - if (blockNumber <= genesisHeight) return; - if (blocksProposed === 1) { - l2Provider.off("block"); - return; - } - - try { - await expect( - onNewL2Block( - l2Provider, - blockNumber, - proposer, - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 - ) - ).not.to.throw; - blocksProposed++; - } catch (e) { - hasFailedAssertions = true; - l2Provider.off("block"); - console.error(e); - throw e; - } - }); - - let blockInfo!: BlockInfo; - - taikoL1.on( - "BlockProposed", - async (id: BigNumber, meta: BlockMetadata) => { - /* eslint-disable-next-line */ - while (blocksProposed < 1) { - await sleep(3 * 1000); - } - try { - const event: BlockProvenEvent = await prover.prove( - await proverSigner.getAddress(), - id.toNumber(), - blockIdsToNumber[id.toString()], - meta - ); - - const proposedBlock = await taikoL1.getProposedBlock(id); - - const forkChoice = await taikoL1.getForkChoice( - id.toNumber(), - event.args.parentHash - ); - - blockInfo = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: event.args.provenAt.toNumber(), - id: event.args.id.toNumber(), - parentHash: event.args.parentHash, - blockHash: event.args.blockHash, - forkChoice: forkChoice, - }; - } catch (e) { - hasFailedAssertions = true; - console.error("proving error", e); - throw e; - } - } - ); - - expect(hasFailedAssertions).to.be.eq(false); - - // wait for block to be proven asynchronously. - - /* eslint-disable-next-line */ - while (!blockInfo) { - await sleep(1 * 1000); - } - - // make sure block is verifiable before we processe - await sleepUntilBlockIsVerifiable( - taikoL1, - blockInfo.id, - blockInfo.provenAt - ); - - const isVerifiable = await taikoL1.isBlockVerifiable( - blockInfo.id, - blockInfo.parentHash - ); - expect(isVerifiable).to.be.eq(true); - const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( - blockInfo.forkChoice.provers[0] - ); - expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); - await verifyBlocks(taikoL1, 1); - - const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( - blockInfo.forkChoice.provers[0] - ); - - // prover should have given given 1 TKO token, since they - // held no TKO balance. - expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); - }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 4a28f2bfb1..5692023ff4 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -1,7 +1,18 @@ import { BigNumber, ethers } from "ethers"; -import { TaikoL1, TkoToken } from "../../typechain"; +import { ConfigManager, TaikoL1, TkoToken } from "../../typechain"; +import deployAddressManager from "../utils/addressManager"; import Proposer from "../utils/proposer"; +import { + getDefaultL2Signer, + getL1Provider, + getL2Provider, +} from "../utils/provider"; +import createAndSeedWallets from "../utils/seed"; import sleep from "../utils/sleep"; +import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; +import { deployTaikoL2 } from "../utils/taikoL2"; +import deployTkoToken from "../utils/tkoToken"; +import { ethers as hardhatEthers } from "hardhat"; type ForkChoice = { provenAt: BigNumber; @@ -73,9 +84,121 @@ const sendTinyEtherToZeroAddress = async (signer: any) => { .wait(1); }; +async function initTokenomicsFixture() { + const l1Provider = getL1Provider(); + + l1Provider.pollingInterval = 100; + + const signers = await hardhatEthers.getSigners(); + const l1Signer = signers[0]; + + const l2Provider = getL2Provider(); + + const l2Signer = await getDefaultL2Signer(); + + const l2AddressManager = await deployAddressManager(l2Signer); + const taikoL2 = await deployTaikoL2(l2Signer, l2AddressManager, false); + + const genesisHash = taikoL2.deployTransaction.blockHash as string; + const genesisHeight = taikoL2.deployTransaction.blockNumber as number; + + const l1AddressManager = await deployAddressManager(l1Signer); + const taikoL1 = await deployTaikoL1( + l1AddressManager, + genesisHash, + true, + defaultFeeBase + ); + const { chainId } = await l1Provider.getNetwork(); + + const [proposerSigner, proverSigner] = await createAndSeedWallets( + 2, + l1Signer + ); + + const tkoTokenL1 = await deployTkoToken( + l1Signer, + l1AddressManager, + taikoL1.address + ); + + await ( + await l1AddressManager.setAddress( + `${chainId}.tko_token`, + tkoTokenL1.address + ) + ).wait(1); + + const { chainId: l2ChainId } = await l2Provider.getNetwork(); + + await ( + await l1AddressManager.setAddress(`${l2ChainId}.taiko`, taikoL2.address) + ).wait(1); + + await ( + await l1AddressManager.setAddress( + `${chainId}.proof_verifier`, + taikoL1.address + ) + ).wait(1); + + const configManager: ConfigManager = await ( + await hardhatEthers.getContractFactory("ConfigManager") + ) + .connect(l1Signer) + .deploy(); + await configManager.deployed(); + + await l1AddressManager.setAddress( + `${chainId}.config_manager`, + configManager.address + ); + + const mintTx = await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proposerSigner.getAddress(), + ethers.utils.parseEther("100") + ); + + 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 + ); + + const tx = await l2Signer.sendTransaction({ + to: proverSigner.address, + value: ethers.utils.parseUnits("1", "ether"), + }); + await tx.wait(1); + + return { + taikoL1, + taikoL2, + l1Provider, + l2Provider, + l1Signer, + l2Signer, + proposerSigner, + proverSigner, + genesisHeight, + genesisHash, + tkoTokenL1, + l1AddressManager, + interval, + }; +} export { sendTinyEtherToZeroAddress, onNewL2Block, sleepUntilBlockIsVerifiable, + initTokenomicsFixture, }; + export type { BlockInfo, ForkChoice }; From 3f17e2ea5c5ee70d42c234e393cbdc8d55c83ccc Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 11:35:29 -0800 Subject: [PATCH 12/45] rm +1 --- packages/protocol/contracts/L1/TaikoL1.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 06761e5e2f..dd9cafd550 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -89,7 +89,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { state: state, config: getConfig(), resolver: AddressResolver(this), - maxBlocks: config.maxVerificationsPerTx + 1, + maxBlocks: config.maxVerificationsPerTx, checkHalt: false }); } From 38e9a77ad3a696936108d277fe402befc92287a5 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 12:02:54 -0800 Subject: [PATCH 13/45] wip --- .../test/tokenomics/proofReward.test.ts | 41 ++++++++++++++++++- packages/protocol/test/tokenomics/utils.ts | 18 ++++---- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index c4bb007528..32af6ba946 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -175,7 +175,8 @@ describe("tokenomics: proofReward", function () { expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); }); - it(`propose blocks, wait til maxNumBlocks is filled. + it(`single prover, single proposer. + propose blocks, wait til maxNumBlocks is filled. proverReward should decline should increase as blocks are proved then verified. the provers TKO balance should increase as the blocks are verified and they receive the proofReward.`, async function () { @@ -198,6 +199,14 @@ describe("tokenomics: proofReward", function () { ethers.utils.parseEther("100") ); + // proposer needs TKO or their deposit refund will be cut down to 0 wei. + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proposerSigner.getAddress(), + ethers.utils.parseEther("100") + ); + const prover = new Prover( taikoL1, taikoL2, @@ -339,6 +348,12 @@ describe("tokenomics: proofReward", function () { const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf(prover); + const proposerAddress = await proposerSigner.getAddress(); + + const proposerTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(proposerAddress); + + expect(proposerTkoBalanceBeforeVerification.gt(0)).to.be.eq(true); const verifiedEvent = await verifyBlocks(taikoL1, 1); expect(verifiedEvent).to.be.not.undefined; @@ -348,6 +363,7 @@ describe("tokenomics: proofReward", function () { const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf(prover); + // prover should have increased in balance as he received the proof reward. expect( proverTkoBalanceAfterVerification.gt( proverTkoBalanceBeforeVerification @@ -359,13 +375,36 @@ describe("tokenomics: proofReward", function () { block.provenAt ); + // last proof reward should be larger than the new proof reward, + // since we have stopped proposing, and slots are growing as we verify. if (lastProofReward.gt(0)) { expect(newProofReward).to.be.lt(lastProofReward); } lastProofReward = newProofReward; + // latest synced header should be our just-verified block hash. const latestHash = await taikoL1.getLatestSyncedHeader(); expect(latestHash).to.be.eq(block.blockHash); + + // fork choice should be nullified via _cleanUp in LibVerifying + const forkChoice = await taikoL1.getForkChoice( + block.id, + block.parentHash + ); + expect(forkChoice.provenAt).to.be.eq(BigNumber.from(0)); + expect(forkChoice.provers).to.be.empty; + expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); + + // proposer should be minted their refund of their deposit back after + // verification, as long as their balance is > 0 + const proposerTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(proposerAddress); + + expect( + proposerTkoBalanceAfterVerification.gt( + proposerTkoBalanceBeforeVerification + ) + ).to.be.eq(true); } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 5692023ff4..ed42f11465 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -84,7 +84,7 @@ const sendTinyEtherToZeroAddress = async (signer: any) => { .wait(1); }; -async function initTokenomicsFixture() { +async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { const l1Provider = getL1Provider(); l1Provider.pollingInterval = 100; @@ -154,14 +154,16 @@ async function initTokenomicsFixture() { configManager.address ); - const mintTx = await tkoTokenL1 - .connect(l1Signer) - .mintAnyone( - await proposerSigner.getAddress(), - ethers.utils.parseEther("100") - ); + if (mintTkoToProposer) { + const mintTx = await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proposerSigner.getAddress(), + ethers.utils.parseEther("100") + ); - await mintTx.wait(1); + await mintTx.wait(1); + } // set up interval mining so we always get new blocks await l2Provider.send("evm_setAutomine", [true]); From 365288b7efac787de0b4ae6c5a85d8cf8ba26f96 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 16:59:08 -0800 Subject: [PATCH 14/45] lower blocks so test runs faster, disable auto verification, comment out proposer tko balance til i make it work --- .../test/L1/TestTaikoL1EnableTokenomics.sol | 4 +- .../test/tokenomics/proofReward.test.ts | 113 +++++++++--------- packages/protocol/test/tokenomics/utils.ts | 2 + 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index 997d1d8744..a646de1910 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -18,12 +18,12 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { { config.chainId = 167; // up to 2048 pending blocks - config.maxNumBlocks = 6; + config.maxNumBlocks = 4; config.blockHashHistory = 10; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 config.zkProofsPerBlock = 1; - config.maxVerificationsPerTx = 1; + config.maxVerificationsPerTx = 0; // dont verify blocks automatically config.commitConfirmations = 1; config.maxProofsPerForkChoice = 5; config.blockMaxGasLimit = 30000000; // TODO diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 32af6ba946..3889c69c85 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -191,21 +191,17 @@ describe("tokenomics: proofReward", function () { 0 ); - // prover needs TKO or their reward will be cut down to 1 wei. - await tkoTokenL1 - .connect(l1Signer) - .mintAnyone( - await proverSigner.getAddress(), - ethers.utils.parseEther("100") - ); + await sleep(5 * 1000); - // proposer needs TKO or their deposit refund will be cut down to 0 wei. - await tkoTokenL1 - .connect(l1Signer) - .mintAnyone( - await proposerSigner.getAddress(), - ethers.utils.parseEther("100") - ); + // prover needs TKO or their reward will be cut down to 1 wei. + await ( + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proverSigner.getAddress(), + ethers.utils.parseEther("100") + ) + ).wait(1); const prover = new Prover( taikoL1, @@ -215,40 +211,6 @@ describe("tokenomics: proofReward", function () { proverSigner ); - let hasFailedAssertions: boolean = false; - let blocksProposed: number = 0; - - const listener = async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - // fill up all slots. - if (blocksProposed === maxNumBlocks.toNumber()) { - l2Provider.off("block", listener); - return; - } - - try { - await expect( - onNewL2Block( - l2Provider, - blockNumber, - proposer, - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 - ) - ).not.to.throw; - blocksProposed++; - } catch (e) { - hasFailedAssertions = true; - l2Provider.off("block", listener); - console.error(e); - throw e; - } - }; - - l2Provider.on("block", listener); - let blocksProved: number = 0; const blockInfo: BlockInfo[] = []; @@ -308,6 +270,43 @@ describe("tokenomics: proofReward", function () { } ); + let hasFailedAssertions: boolean = false; + let blocksProposed: number = 0; + + const listener = async (blockNumber: number) => { + if (blockNumber <= genesisHeight) return; + // fill up all slots. + if (blocksProposed === maxNumBlocks.toNumber()) { + l2Provider.off("block", listener); + return; + } + + try { + console.log("proposing"); + await expect( + onNewL2Block( + l2Provider, + blockNumber, + proposer, + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ) + ).not.to.throw; + blocksProposed++; + + console.log("proposed", blocksProposed); + } catch (e) { + hasFailedAssertions = true; + l2Provider.off("block", listener); + console.error(e); + throw e; + } + }; + + l2Provider.on("block", listener); + // wait for all blocks to be proven /* eslint-disable-next-line */ while (blocksProved < maxNumBlocks.toNumber() - 1) { @@ -395,16 +394,16 @@ describe("tokenomics: proofReward", function () { expect(forkChoice.provers).to.be.empty; expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); - // proposer should be minted their refund of their deposit back after - // verification, as long as their balance is > 0 - const proposerTkoBalanceAfterVerification = - await tkoTokenL1.balanceOf(proposerAddress); + // // proposer should be minted their refund of their deposit back after + // // verification, as long as their balance is > 0 + // const proposerTkoBalanceAfterVerification = + // await tkoTokenL1.balanceOf(proposerAddress); - expect( - proposerTkoBalanceAfterVerification.gt( - proposerTkoBalanceBeforeVerification - ) - ).to.be.eq(true); + // expect( + // proposerTkoBalanceAfterVerification.gt( + // proposerTkoBalanceBeforeVerification + // ) + // ).to.be.eq(true); } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index ed42f11465..2b1da8cc55 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -72,6 +72,8 @@ async function onNewL2Block( const newBlockFee = await taikoL1.getBlockFee(); + console.log("-------------------proposed----------", id); + return { newProposerTkoBalance, newBlockFee, newProofReward }; } From 04792f1b6c8af75001bc6ea4b61df3db9d151bea Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Tue, 24 Jan 2023 17:43:02 -0800 Subject: [PATCH 15/45] remove async event nature of tests --- .../contracts/L1/libs/LibProposing.sol | 1 + .../test/L1/TestTaikoL1EnableTokenomics.sol | 2 +- .../test/tokenomics/proofReward.test.ts | 185 ++++++++---------- packages/protocol/test/tokenomics/utils.ts | 17 +- packages/protocol/test/utils/proposer.ts | 2 +- 5 files changed, 101 insertions(+), 106 deletions(-) diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index b6cbc9b509..ae66c1001e 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -89,6 +89,7 @@ library LibProposing { meta.txListHash == txList.hashTxList(), "L1:txList" ); + // require(4 < 0 + 4) require( state.nextBlockId < state.latestVerifiedId + config.maxNumBlocks, diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index a646de1910..fd1c194eae 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -18,7 +18,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { { config.chainId = 167; // up to 2048 pending blocks - config.maxNumBlocks = 4; + config.maxNumBlocks = 5; config.blockHashHistory = 10; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 3889c69c85..1eb111cfe4 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import { TaikoL1, TaikoL2 } from "../../typechain"; +import { BlockProposedEvent } from "../../typechain/LibProposing"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import { BlockMetadata } from "../utils/block_metadata"; @@ -130,6 +131,7 @@ describe("tokenomics: proofReward", function () { parentHash: event.args.parentHash, blockHash: event.args.blockHash, forkChoice: forkChoice, + deposit: proposedBlock.deposit, }; } catch (e) { hasFailedAssertions = true; @@ -191,7 +193,13 @@ describe("tokenomics: proofReward", function () { 0 ); - await sleep(5 * 1000); + const prover = new Prover( + taikoL1, + taikoL2, + l1Provider, + l2Provider, + proverSigner + ); // prover needs TKO or their reward will be cut down to 1 wei. await ( @@ -203,100 +211,31 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); - const prover = new Prover( - taikoL1, - taikoL2, - l1Provider, - l2Provider, - proverSigner - ); - - let blocksProved: number = 0; - - const blockInfo: BlockInfo[] = []; - - taikoL1.on( - "BlockProposed", - async (id: BigNumber, meta: BlockMetadata) => { - // wait until we fill up all slots, so we can - // then prove blocks in order, and each time a block is proven then verified, - // we can expect the proofReward to go down as slots become available. - - console.log("p", id.toNumber()); - while (blocksProposed < maxNumBlocks.toNumber()) { - await sleep(3 * 1000); - } - - try { - const proverAddress = await proverSigner.getAddress(); - const { args } = await prover.prove( - await proverSigner.getAddress(), - id.toNumber(), - blockIdsToNumber[id.toString()], - meta - ); - const { - blockHash, - id: blockId, - parentHash, - provenAt, - } = args; - - const proposedBlock = await taikoL1.getProposedBlock(id); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq(proverAddress); - - blockInfo.push({ - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - }); - blocksProved++; - } catch (e) { - hasFailedAssertions = true; - console.error("proving error", e); - throw e; - } - } - ); - let hasFailedAssertions: boolean = false; - let blocksProposed: number = 0; + const blocksProposed: BlockProposedEvent[] = []; const listener = async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; // fill up all slots. - if (blocksProposed === maxNumBlocks.toNumber()) { + if (blocksProposed.length === maxNumBlocks.toNumber() - 1) { l2Provider.off("block", listener); return; } try { - console.log("proposing"); - await expect( - onNewL2Block( - l2Provider, - blockNumber, - proposer, - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 - ) - ).not.to.throw; - blocksProposed++; - - console.log("proposed", blocksProposed); + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + blocksProposed.push(proposedEvent); + + console.log("proposed", proposedEvent.args.id); } catch (e) { hasFailedAssertions = true; l2Provider.off("block", listener); @@ -307,21 +246,65 @@ describe("tokenomics: proofReward", function () { l2Provider.on("block", listener); - // wait for all blocks to be proven - /* eslint-disable-next-line */ - while (blocksProved < maxNumBlocks.toNumber() - 1) { + while (blocksProposed.length < maxNumBlocks.toNumber() - 1) { await sleep(3 * 1000); } expect(hasFailedAssertions).to.be.eq(false); + const provedBlocks: BlockInfo[] = []; + + await Promise.all( + blocksProposed.map(async (block) => { + console.log("proving block", block); + const proverAddress = await proverSigner.getAddress(); + const { args } = await prover.prove( + await proverSigner.getAddress(), + block.args.id.toNumber(), + blockIdsToNumber[block.args.id.toString()], + block.args.meta as any as BlockMetadata + ); + const { blockHash, id: blockId, parentHash, provenAt } = args; + + const proposedBlock = await taikoL1.getProposedBlock( + block.args.id.toNumber() + ); + + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq(proverAddress); + + provedBlocks.push({ + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: block.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + }); + }) + ); + + // wait for all blocks to be proven + /* eslint-disable-next-line */ + while (provedBlocks.length < maxNumBlocks.toNumber() - 1) { + await sleep(3 * 1000); + } + let lastProofReward: BigNumber = BigNumber.from(0); // now try to verify the blocks and make sure the proof reward shrinks as slots // free up - for (let i = 1; i < blockInfo.length + 1; i++) { - const block = blockInfo.find((b) => b.id === i) as any as BlockInfo; + for (let i = 0; i < provedBlocks.length; i++) { + const block = provedBlocks[i]; expect(block).not.to.be.undefined; + console.log("verifying blocks", block); await sleepUntilBlockIsVerifiable( taikoL1, @@ -394,16 +377,18 @@ describe("tokenomics: proofReward", function () { expect(forkChoice.provers).to.be.empty; expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); - // // proposer should be minted their refund of their deposit back after - // // verification, as long as their balance is > 0 - // const proposerTkoBalanceAfterVerification = - // await tkoTokenL1.balanceOf(proposerAddress); - - // expect( - // proposerTkoBalanceAfterVerification.gt( - // proposerTkoBalanceBeforeVerification - // ) - // ).to.be.eq(true); + // proposer should be minted their refund of their deposit back after + // verification, as long as their balance is > 0; + + // if (block.deposit.gt(0)) { + // const proposerTkoBalanceAfterVerification = + // await tkoTokenL1.balanceOf(proposerAddress); + // expect( + // proposerTkoBalanceAfterVerification.gt( + // proposerTkoBalanceBeforeVerification + // ) + // ).to.be.eq(true); + // } } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 2b1da8cc55..577a74a7af 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -13,6 +13,7 @@ import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import { ethers as hardhatEthers } from "hardhat"; +import { BlockProposedEvent } from "../../typechain/LibProposing"; type ForkChoice = { provenAt: BigNumber; @@ -27,6 +28,7 @@ type BlockInfo = { parentHash: string; blockHash: string; forkChoice: ForkChoice; + deposit: BigNumber; }; async function sleepUntilBlockIsVerifiable( @@ -36,8 +38,9 @@ async function sleepUntilBlockIsVerifiable( ) { const delay = await taikoL1.getUncleProofDelay(id); const delayInMs = delay.mul(1000); - await sleep(5 * delayInMs.toNumber()); + await sleep(5 * delayInMs.toNumber()); // TODO: use provenAt, calc difference, etc } + async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, blockNumber: number, @@ -47,17 +50,18 @@ async function onNewL2Block( proposerSigner: any, tkoTokenL1: TkoToken ): Promise<{ + proposedEvent: BlockProposedEvent; newProposerTkoBalance: BigNumber; newBlockFee: BigNumber; newProofReward: BigNumber; }> { const block = await l2Provider.getBlock(blockNumber); const receipt = await proposer.commitThenProposeBlock(block); - const proposedEvent = (receipt.events as any[]).find( + const proposedEvent: BlockProposedEvent = (receipt.events as any[]).find( (e) => e.event === "BlockProposed" ); - const { id, meta } = (proposedEvent as any).args; + const { id, meta } = proposedEvent.args; blockIdsToNumber[id.toString()] = block.number; @@ -74,7 +78,12 @@ async function onNewL2Block( console.log("-------------------proposed----------", id); - return { newProposerTkoBalance, newBlockFee, newProofReward }; + return { + proposedEvent, + newProposerTkoBalance, + newBlockFee, + newProofReward, + }; } const sendTinyEtherToZeroAddress = async (signer: any) => { diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index bda263f600..4c101880dd 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -29,7 +29,7 @@ class Proposer { async commitThenProposeBlock(block?: ethers.providers.Block) { while (this.proposingMutex) { - await sleep(100); + await sleep(3 * 1000); } this.proposingMutex = true; if (!block) block = await this.l2Provider.getBlock("latest"); From cf12914203ac45a2aba1278e154f60cc1406c43f Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 25 Jan 2023 18:12:23 -0800 Subject: [PATCH 16/45] add multiple proposer/prover test, addtl utils methods --- .../test/L1/TestTaikoL1EnableTokenomics.sol | 2 +- .../protocol/test/tokenomics/blockFee.test.ts | 13 +- .../test/tokenomics/proofReward.test.ts | 244 +++++++++++------- packages/protocol/test/tokenomics/utils.ts | 84 ++++++ packages/protocol/test/utils/proposer.ts | 9 +- packages/protocol/test/utils/prover.ts | 18 +- packages/protocol/test/utils/seed.ts | 4 +- 7 files changed, 272 insertions(+), 102 deletions(-) diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index fd1c194eae..1612a7b64c 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -18,7 +18,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { { config.chainId = 167; // up to 2048 pending blocks - config.maxNumBlocks = 5; + config.maxNumBlocks = 6; config.blockHashHistory = 10; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 24d92b1eac..ff40231185 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -69,7 +69,8 @@ describe("tokenomics: blockFee", function () { l2Provider, commitConfirmations.toNumber(), maxNumBlocks.toNumber(), - 0 + 0, + proposerSigner ); // get the initiaal tkoBalance, which should decrease every block proposal @@ -79,6 +80,8 @@ describe("tokenomics: blockFee", function () { // do the same for the blockFee, which should increase every block proposal // with proofs not being submitted. + // we want to wait for enough blocks until the blockFee is no longer 0, then run our + // tests. let lastBlockFee = await taikoL1.getBlockFee(); while (lastBlockFee.eq(0)) { await sleep(500); @@ -88,6 +91,7 @@ describe("tokenomics: blockFee", function () { let lastProofReward = BigNumber.from(0); let hasFailedAssertions: boolean = false; + let blocksProposed: number = 0; // every time a l2 block is created, we should try to propose it on L1. l2Provider.on("block", async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; @@ -112,6 +116,7 @@ describe("tokenomics: blockFee", function () { lastBlockFee = newBlockFee; lastProofReward = newProofReward; lastProposerTkoBalance = newProposerTkoBalance; + blocksProposed++; } catch (e) { hasFailedAssertions = true; console.error(e); @@ -119,7 +124,11 @@ describe("tokenomics: blockFee", function () { } }); - await sleep(20 * 1000); + // wait until one roudn of blocks are proposed, then expect none of them to have + // failed their assertions. + while (blocksProposed < maxNumBlocks.toNumber() - 1) { + await sleep(1 * 1000); + } l2Provider.off("block"); expect(hasFailedAssertions).to.be.eq(false); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 1eb111cfe4..e73be02bd9 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,12 +1,13 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; -import { TaikoL1, TaikoL2 } from "../../typechain"; +import { TaikoL1 } from "../../typechain"; import { BlockProposedEvent } from "../../typechain/LibProposing"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import { BlockMetadata } from "../utils/block_metadata"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; +import createAndSeedWallets from "../utils/seed"; // import Prover from "../utils/prover"; import sleep from "../utils/sleep"; @@ -15,13 +16,13 @@ import { BlockInfo, initTokenomicsFixture, onNewL2Block, + randEle, sleepUntilBlockIsVerifiable, + verifyBlockAndAssert, } from "./utils"; describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; - let taikoL2: TaikoL2; - let l1Provider: ethers.providers.JsonRpcProvider; let l2Provider: ethers.providers.JsonRpcProvider; let l1Signer: any; let proposerSigner: any; @@ -33,8 +34,6 @@ describe("tokenomics: proofReward", function () { beforeEach(async () => { ({ taikoL1, - taikoL2, - l1Provider, l2Provider, l1Signer, proposerSigner, @@ -56,16 +55,11 @@ describe("tokenomics: proofReward", function () { l2Provider, commitConfirmations.toNumber(), maxNumBlocks.toNumber(), - 0 + 0, + proposerSigner ); - const prover = new Prover( - taikoL1, - taikoL2, - l1Provider, - l2Provider, - proverSigner - ); + const prover = new Prover(taikoL1, l2Provider, proverSigner); let hasFailedAssertions: boolean = false; let blocksProposed: number = 0; @@ -132,6 +126,7 @@ describe("tokenomics: proofReward", function () { blockHash: event.args.blockHash, forkChoice: forkChoice, deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, }; } catch (e) { hasFailedAssertions = true; @@ -166,6 +161,7 @@ describe("tokenomics: proofReward", function () { blockInfo.forkChoice.provers[0] ); expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); + await verifyBlocks(taikoL1, 1); const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( @@ -181,7 +177,8 @@ describe("tokenomics: proofReward", function () { propose blocks, wait til maxNumBlocks is filled. proverReward should decline should increase as blocks are proved then verified. the provers TKO balance should increase as the blocks are verified and - they receive the proofReward.`, async function () { + they receive the proofReward. + the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); const blockIdsToNumber: any = {}; @@ -190,16 +187,11 @@ describe("tokenomics: proofReward", function () { l2Provider, commitConfirmations.toNumber(), maxNumBlocks.toNumber(), - 0 + 0, + proposerSigner ); - const prover = new Prover( - taikoL1, - taikoL2, - l1Provider, - l2Provider, - proverSigner - ); + const prover = new Prover(taikoL1, l2Provider, proverSigner); // prover needs TKO or their reward will be cut down to 1 wei. await ( @@ -256,7 +248,6 @@ describe("tokenomics: proofReward", function () { await Promise.all( blocksProposed.map(async (block) => { - console.log("proving block", block); const proverAddress = await proverSigner.getAddress(); const { args } = await prover.prove( await proverSigner.getAddress(), @@ -270,6 +261,8 @@ describe("tokenomics: proofReward", function () { block.args.id.toNumber() ); + console.log("proposed block", proposedBlock); + const forkChoice = await taikoL1.getForkChoice( blockId.toNumber(), parentHash @@ -287,6 +280,7 @@ describe("tokenomics: proofReward", function () { blockHash: blockHash, forkChoice: forkChoice, deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, }); }) ); @@ -306,89 +300,165 @@ describe("tokenomics: proofReward", function () { expect(block).not.to.be.undefined; console.log("verifying blocks", block); - await sleepUntilBlockIsVerifiable( + const { newProofReward } = await verifyBlockAndAssert( taikoL1, - block.id, - block.provenAt + tkoTokenL1, + block, + lastProofReward, + i > 0 ); + lastProofReward = newProofReward; + } + }); - const isVerifiable = await taikoL1.isBlockVerifiable( - block.id, - block.parentHash - ); + it(`multiple provers, multiple proposers. + propose blocks, wait til maxNumBlocks is filled. + proverReward should decline should increase as blocks are proved then verified. + the provers TKO balance should increase as the blocks are verified and + they receive the proofReward. + the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { + const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); + const blockIdsToNumber: any = {}; - expect(isVerifiable).to.be.eq(true); + const proposers = (await createAndSeedWallets(3, l1Signer)).map( + (p: ethers.Wallet) => + new Proposer( + taikoL1.connect(p), + l2Provider, + commitConfirmations.toNumber(), + maxNumBlocks.toNumber(), + 0, + p + ) + ); - // dont verify first blocks parent hash, because we arent "real L2" in these - // tests, the parent hash will be wrong. - if (i > 1) { - const latestHash = await taikoL1.getLatestSyncedHeader(); - expect(latestHash).to.be.eq(block.parentHash); - } - const prover = block.forkChoice.provers[0]; + const provers = (await createAndSeedWallets(3, l1Signer)).map( + (p: ethers.Wallet) => new Prover(taikoL1, l2Provider, p) + ); - const proverTkoBalanceBeforeVerification = - await tkoTokenL1.balanceOf(prover); + for (const prover of provers) { + await ( + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await prover.getSigner().getAddress(), + ethers.utils.parseEther("10000") + ) + ).wait(1); + } + for (const proposer of proposers) { + await ( + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proposer.getSigner().getAddress(), + ethers.utils.parseEther("10000") + ) + ).wait(1); + } + let hasFailedAssertions: boolean = false; + const blocksProposed: BlockProposedEvent[] = []; - const proposerAddress = await proposerSigner.getAddress(); + const listener = async (blockNumber: number) => { + if (blockNumber <= genesisHeight) return; + // fill up all slots. + if (blocksProposed.length === maxNumBlocks.toNumber() - 1) { + l2Provider.off("block", listener); + return; + } - const proposerTkoBalanceBeforeVerification = - await tkoTokenL1.balanceOf(proposerAddress); + try { + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + randEle(proposers), + blockIdsToNumber, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + blocksProposed.push(proposedEvent); + + console.log("proposed", proposedEvent.args.id); + } catch (e) { + hasFailedAssertions = true; + l2Provider.off("block", listener); + console.error(e); + throw e; + } + }; - expect(proposerTkoBalanceBeforeVerification.gt(0)).to.be.eq(true); - const verifiedEvent = await verifyBlocks(taikoL1, 1); - expect(verifiedEvent).to.be.not.undefined; + l2Provider.on("block", listener); - expect(verifiedEvent.args.blockHash).to.be.eq(block.blockHash); - expect(verifiedEvent.args.id.eq(block.id)).to.be.eq(true); + while (blocksProposed.length < maxNumBlocks.toNumber() - 1) { + await sleep(3 * 1000); + } - const proverTkoBalanceAfterVerification = - await tkoTokenL1.balanceOf(prover); + expect(hasFailedAssertions).to.be.eq(false); - // prover should have increased in balance as he received the proof reward. - expect( - proverTkoBalanceAfterVerification.gt( - proverTkoBalanceBeforeVerification - ) - ).to.be.eq(true); + const provedBlocks: BlockInfo[] = []; - const newProofReward = await taikoL1.getProofReward( - block.proposedAt, - block.provenAt + for (const block of blocksProposed) { + const proverAddress = await proverSigner.getAddress(); + const { args } = await randEle(provers).prove( + await proverSigner.getAddress(), + block.args.id.toNumber(), + blockIdsToNumber[block.args.id.toString()], + block.args.meta as any as BlockMetadata ); + const { blockHash, id: blockId, parentHash, provenAt } = args; - // last proof reward should be larger than the new proof reward, - // since we have stopped proposing, and slots are growing as we verify. - if (lastProofReward.gt(0)) { - expect(newProofReward).to.be.lt(lastProofReward); - } - lastProofReward = newProofReward; + const proposedBlock = await taikoL1.getProposedBlock( + block.args.id.toNumber() + ); - // latest synced header should be our just-verified block hash. - const latestHash = await taikoL1.getLatestSyncedHeader(); - expect(latestHash).to.be.eq(block.blockHash); + console.log("proposed block", proposedBlock); - // fork choice should be nullified via _cleanUp in LibVerifying const forkChoice = await taikoL1.getForkChoice( - block.id, - block.parentHash + blockId.toNumber(), + parentHash ); - expect(forkChoice.provenAt).to.be.eq(BigNumber.from(0)); - expect(forkChoice.provers).to.be.empty; - expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); - - // proposer should be minted their refund of their deposit back after - // verification, as long as their balance is > 0; - - // if (block.deposit.gt(0)) { - // const proposerTkoBalanceAfterVerification = - // await tkoTokenL1.balanceOf(proposerAddress); - // expect( - // proposerTkoBalanceAfterVerification.gt( - // proposerTkoBalanceBeforeVerification - // ) - // ).to.be.eq(true); - // } + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq(proverAddress); + + provedBlocks.push({ + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: block.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }); + } + + // wait for all blocks to be proven + /* eslint-disable-next-line */ + while (provedBlocks.length < maxNumBlocks.toNumber() - 1) { + await sleep(3 * 1000); + } + + let lastProofReward: BigNumber = BigNumber.from(0); + + // now try to verify the blocks and make sure the proof reward shrinks as slots + // free up + for (let i = 0; i < provedBlocks.length; i++) { + const block = provedBlocks[i]; + console.log("proving block", block); + expect(block).not.to.be.undefined; + + const { newProofReward } = await verifyBlockAndAssert( + taikoL1, + tkoTokenL1, + block, + lastProofReward, + i > 0 + ); + lastProofReward = newProofReward; } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 577a74a7af..acbbfeb106 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -14,6 +14,8 @@ import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import { ethers as hardhatEthers } from "hardhat"; import { BlockProposedEvent } from "../../typechain/LibProposing"; +import { expect } from "chai"; +import verifyBlocks from "../utils/verify"; type ForkChoice = { provenAt: BigNumber; @@ -29,6 +31,7 @@ type BlockInfo = { blockHash: string; forkChoice: ForkChoice; deposit: BigNumber; + proposer: string; }; async function sleepUntilBlockIsVerifiable( @@ -207,11 +210,92 @@ async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { interval, }; } + +async function verifyBlockAndAssert( + taikoL1: TaikoL1, + tkoTokenL1: TkoToken, + block: BlockInfo, + lastProofReward: BigNumber, + compareHeaders: boolean = false +): Promise<{ newProofReward: BigNumber }> { + await sleepUntilBlockIsVerifiable(taikoL1, block.id, block.provenAt); + + const isVerifiable = await taikoL1.isBlockVerifiable( + block.id, + block.parentHash + ); + + expect(isVerifiable).to.be.eq(true); + + // dont verify first blocks parent hash, because we arent "real L2" in these + // tests, the parent hash will be wrong. + // if (compareHeaders) { + // const latestHash = await taikoL1.getLatestSyncedHeader(); + // expect(latestHash).to.be.eq(block.parentHash); + // } + const prover = block.forkChoice.provers[0]; + + const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + prover + ); + + const proposerTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + block.proposer + ); + + expect(proposerTkoBalanceBeforeVerification.gt(0)).to.be.eq(true); + const verifiedEvent = await verifyBlocks(taikoL1, 1); + expect(verifiedEvent).to.be.not.undefined; + + expect(verifiedEvent.args.blockHash).to.be.eq(block.blockHash); + expect(verifiedEvent.args.id.eq(block.id)).to.be.eq(true); + + const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( + prover + ); + + // prover should have increased in balance as he received the proof reward. + expect( + proverTkoBalanceAfterVerification.gt(proverTkoBalanceBeforeVerification) + ).to.be.eq(true); + + const newProofReward = await taikoL1.getProofReward( + block.proposedAt, + block.provenAt + ); + + // last proof reward should be larger than the new proof reward, + // since we have stopped proposing, and slots are growing as we verify. + if (lastProofReward.gt(0)) { + expect(newProofReward).to.be.lt(lastProofReward); + } + + // latest synced header should be our just-verified block hash. + const latestHash = await taikoL1.getLatestSyncedHeader(); + expect(latestHash).to.be.eq(block.blockHash); + + // fork choice should be nullified via _cleanUp in LibVerifying + const forkChoice = await taikoL1.getForkChoice(block.id, block.parentHash); + expect(forkChoice.provenAt).to.be.eq(BigNumber.from(0)); + expect(forkChoice.provers).to.be.empty; + expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); + + // proposer should be minted their refund of their deposit back after + // verification, as long as their balance is > 0; + return { newProofReward }; +} + +function randEle(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + export { sendTinyEtherToZeroAddress, onNewL2Block, sleepUntilBlockIsVerifiable, initTokenomicsFixture, + verifyBlockAndAssert, + randEle, }; export type { BlockInfo, ForkChoice }; diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index 4c101880dd..f8b919bfb1 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -10,6 +10,7 @@ class Proposer { private readonly commitConfirms: number; private readonly maxNumBlocks: number; private nextCommitSlot: number; + private signer: ethers.Wallet; private proposingMutex: boolean = false; @@ -18,13 +19,19 @@ class Proposer { l2Provider: ethers.providers.JsonRpcProvider, commitConfirms: number, maxNumBlocks: number, - initialCommitSlot: number + initialCommitSlot: number, + signer: ethers.Wallet ) { this.taikoL1 = taikoL1; this.l2Provider = l2Provider; this.commitConfirms = commitConfirms; this.maxNumBlocks = maxNumBlocks; this.nextCommitSlot = initialCommitSlot; + this.signer = signer; + } + + getSigner() { + return this.signer; } async commitThenProposeBlock(block?: ethers.providers.Block) { diff --git a/packages/protocol/test/utils/prover.ts b/packages/protocol/test/utils/prover.ts index 373fc94bde..1c9960d173 100644 --- a/packages/protocol/test/utils/prover.ts +++ b/packages/protocol/test/utils/prover.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { TaikoL1, TaikoL2 } from "../../typechain"; +import { TaikoL1 } from "../../typechain"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { proveBlock } from "./prove"; @@ -7,24 +7,22 @@ import sleep from "./sleep"; class Prover { private readonly taikoL1: TaikoL1; - private readonly taikoL2: TaikoL2; - private readonly l1Provider: ethers.providers.JsonRpcProvider; private readonly l2Provider: ethers.providers.JsonRpcProvider; - private readonly l2Signer: ethers.Signer; private provingMutex: boolean = false; + private readonly signer: ethers.Wallet; constructor( taikoL1: TaikoL1, - taikoL2: TaikoL2, - l1Provider: ethers.providers.JsonRpcProvider, l2Provider: ethers.providers.JsonRpcProvider, - l2Signer: ethers.Signer + signer: ethers.Wallet ) { this.taikoL1 = taikoL1; - this.taikoL2 = taikoL2; - this.l1Provider = l1Provider; this.l2Provider = l2Provider; - this.l2Signer = l2Signer; + this.signer = signer; + } + + getSigner() { + return this.signer; } async prove( diff --git a/packages/protocol/test/utils/seed.ts b/packages/protocol/test/utils/seed.ts index 83665b3ba1..12b12775ec 100644 --- a/packages/protocol/test/utils/seed.ts +++ b/packages/protocol/test/utils/seed.ts @@ -7,7 +7,9 @@ const createAndSeedWallets = async ( ): Promise => { const wallets: ethers.Wallet[] = []; for (let i = 0; i < len; i++) { - const wallet = ethers.Wallet.createRandom().connect(signer.provider); + const wallet = ethers.Wallet.createRandom({ + extraEntropy: ethers.utils.randomBytes(32), + }).connect(signer.provider); const tx = await signer.sendTransaction({ to: await wallet.getAddress(), value: amount, From 59072c0bd96cc34deacd418e3ed35e92ae9c75f0 Mon Sep 17 00:00:00 2001 From: Roger <50648015+RogerLamTd@users.noreply.github.com> Date: Wed, 25 Jan 2023 21:52:17 -0500 Subject: [PATCH 17/45] Update protocol.yml enable tokenomics test on branch --- .github/workflows/protocol.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/protocol.yml b/.github/workflows/protocol.yml index 7dd2ecbe2a..835cf9e914 100644 --- a/.github/workflows/protocol.yml +++ b/.github/workflows/protocol.yml @@ -32,9 +32,9 @@ jobs: working-directory: ./packages/protocol run: pnpm test:integration - # - name: protocol - Tokenomics Tests - # working-directory: ./packages/protocol - # run: pnpm test:tokenomics + - name: protocol - Tokenomics Tests + working-directory: ./packages/protocol + run: pnpm test:tokenomics - name: protocol - Test Coverage working-directory: ./packages/protocol From fed052fecec08d956ebe8d2faeea08adb4c2a8c5 Mon Sep 17 00:00:00 2001 From: jeff <113397187+cyberhorsey@users.noreply.github.com> Date: Wed, 25 Jan 2023 19:58:11 -0800 Subject: [PATCH 18/45] Update packages/protocol/test/tokenomics/proofReward.test.ts Co-authored-by: David --- packages/protocol/test/tokenomics/proofReward.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index e73be02bd9..df2e693635 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -8,8 +8,6 @@ import { BlockMetadata } from "../utils/block_metadata"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; import createAndSeedWallets from "../utils/seed"; -// import Prover from "../utils/prover"; - import sleep from "../utils/sleep"; import verifyBlocks from "../utils/verify"; import { From 173012631508137e5e4ba3921f749c1135cb6fb5 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 26 Jan 2023 10:54:53 -0800 Subject: [PATCH 19/45] test names --- packages/protocol/test/tokenomics/blockFee.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index ff40231185..281bb81bca 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -33,11 +33,11 @@ describe("tokenomics: blockFee", function () { afterEach(() => clearInterval(interval)); - it("expects the blockFee to go be 0 when no periods have passed", async function () { + it("expects getBlockFee to return the initial feeBase at time of contract deployment", async function () { // deploy a new instance of TaikoL1 so no blocks have passed. const tL1 = await deployTaikoL1(l1AddressManager, genesisHash, true); const blockFee = await tL1.getBlockFee(); - expect(blockFee.eq(0)).to.be.eq(true); + expect(blockFee).to.be.eq(0); }); it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { @@ -58,7 +58,7 @@ describe("tokenomics: blockFee", function () { } }); - it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since slots are growing and no proofs have been submitted", async function () { + it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since more slots are used and no proofs have been submitted", async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); // wait for one period of halving to occur, so fee is not 0. const blockIdsToNumber: any = {}; From 29bb42c3bfff5355eb1fe910991a2a11ef49e4a2 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 26 Jan 2023 14:37:47 -0800 Subject: [PATCH 20/45] switch to event emitters --- .../protocol/test/tokenomics/blockFee.test.ts | 4 - .../test/tokenomics/proofReward.test.ts | 439 ++++++++---------- packages/protocol/test/tokenomics/utils.ts | 10 +- 3 files changed, 193 insertions(+), 260 deletions(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 281bb81bca..3da5e5b522 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -60,8 +60,6 @@ describe("tokenomics: blockFee", function () { it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since more slots are used and no proofs have been submitted", async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - // wait for one period of halving to occur, so fee is not 0. - const blockIdsToNumber: any = {}; // set up a proposer to continually propose new blocks const proposer = new Proposer( @@ -101,7 +99,6 @@ describe("tokenomics: blockFee", function () { l2Provider, blockNumber, proposer, - blockIdsToNumber, taikoL1, proposerSigner, tkoTokenL1 @@ -119,7 +116,6 @@ describe("tokenomics: blockFee", function () { blocksProposed++; } catch (e) { hasFailedAssertions = true; - console.error(e); throw e; } }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index df2e693635..9268e28558 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; +import EventEmitter from "events"; import { TaikoL1 } from "../../typechain"; import { BlockProposedEvent } from "../../typechain/LibProposing"; import { BlockProvenEvent } from "../../typechain/LibProving"; @@ -12,6 +13,8 @@ import sleep from "../utils/sleep"; import verifyBlocks from "../utils/verify"; import { BlockInfo, + BLOCK_PROPOSED_EVENT, + BLOCK_PROVEN_EVENT, initTokenomicsFixture, onNewL2Block, randEle, @@ -46,7 +49,6 @@ describe("tokenomics: proofReward", function () { it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const blockIdsToNumber: any = {}; const proposer = new Proposer( taikoL1.connect(proposerSigner), @@ -59,116 +61,89 @@ describe("tokenomics: proofReward", function () { const prover = new Prover(taikoL1, l2Provider, proverSigner); - let hasFailedAssertions: boolean = false; - let blocksProposed: number = 0; - - const listener = async (blockNumber: number) => { + const eventEmitter = new EventEmitter(); + l2Provider.on("block", async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; - if (blocksProposed === 1) { - l2Provider.off("block", listener); - return; - } - try { - await expect( - onNewL2Block( - l2Provider, - blockNumber, - proposer, - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 - ) - ).not.to.throw; - blocksProposed++; - } catch (e) { - hasFailedAssertions = true; - l2Provider.off("block", listener); - console.error(e); - throw e; - } - }; - - l2Provider.on("block", listener); - - let blockInfo!: BlockInfo; - - taikoL1.on( - "BlockProposed", - async (id: BigNumber, meta: BlockMetadata) => { - /* eslint-disable-next-line */ - while (blocksProposed < 1) { - await sleep(3 * 1000); - } - try { - const event: BlockProvenEvent = await prover.prove( - await proverSigner.getAddress(), - id.toNumber(), - blockIdsToNumber[id.toString()], - meta - ); - - const proposedBlock = await taikoL1.getProposedBlock(id); - - const forkChoice = await taikoL1.getForkChoice( - id.toNumber(), - event.args.parentHash - ); - - blockInfo = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: event.args.provenAt.toNumber(), - id: event.args.id.toNumber(), - parentHash: event.args.parentHash, - blockHash: event.args.blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - } catch (e) { - hasFailedAssertions = true; - console.error("proving error", e); - throw e; - } - } - ); + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + }); + + eventEmitter.on( + BLOCK_PROPOSED_EVENT, + async ( + proposedBlockEvent: BlockProposedEvent, + blockNumber: number + ) => { + const event: BlockProvenEvent = await prover.prove( + await proverSigner.getAddress(), + proposedBlockEvent.args.id.toNumber(), + blockNumber, + proposedBlockEvent.args.meta as any + ); - expect(hasFailedAssertions).to.be.eq(false); + const proposedBlock = await taikoL1.getProposedBlock( + proposedBlockEvent.args.id.toNumber() + ); - // wait for block to be proven asynchronously. + const forkChoice = await taikoL1.getForkChoice( + proposedBlockEvent.args.id.toNumber(), + event.args.parentHash + ); - /* eslint-disable-next-line */ - while (!blockInfo) { - await sleep(1 * 1000); - } + const blockInfo = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: event.args.provenAt.toNumber(), + id: event.args.id.toNumber(), + parentHash: event.args.parentHash, + blockHash: event.args.blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; - // make sure block is verifiable before we processe - await sleepUntilBlockIsVerifiable( - taikoL1, - blockInfo.id, - blockInfo.provenAt + eventEmitter.emit(BLOCK_PROVEN_EVENT, blockInfo); + } ); - const isVerifiable = await taikoL1.isBlockVerifiable( - blockInfo.id, - blockInfo.parentHash - ); - expect(isVerifiable).to.be.eq(true); - const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( - blockInfo.forkChoice.provers[0] - ); - expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); + eventEmitter.on( + BLOCK_PROVEN_EVENT, + async function (blockInfo: BlockInfo) { + // make sure block is verifiable before we processe + await sleepUntilBlockIsVerifiable( + taikoL1, + blockInfo.id, + blockInfo.provenAt + ); + + const isVerifiable = await taikoL1.isBlockVerifiable( + blockInfo.id, + blockInfo.parentHash + ); + expect(isVerifiable).to.be.eq(true); + const proverTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(blockInfo.forkChoice.provers[0]); + expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); - await verifyBlocks(taikoL1, 1); + await verifyBlocks(taikoL1, 1); - const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( - blockInfo.forkChoice.provers[0] - ); + const proverTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(blockInfo.forkChoice.provers[0]); - // prover should have given given 1 TKO token, since they - // held no TKO balance. - expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); + // prover should have given given 1 TKO token, since they + // held no TKO balance. + expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); + } + ); }); it(`single prover, single proposer. @@ -178,7 +153,6 @@ describe("tokenomics: proofReward", function () { they receive the proofReward. the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const blockIdsToNumber: any = {}; const proposer = new Proposer( taikoL1.connect(proposerSigner), @@ -201,62 +175,41 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); - let hasFailedAssertions: boolean = false; - const blocksProposed: BlockProposedEvent[] = []; - - const listener = async (blockNumber: number) => { + const eventEmitter = new EventEmitter(); + l2Provider.on("block", async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; - // fill up all slots. - if (blocksProposed.length === maxNumBlocks.toNumber() - 1) { - l2Provider.off("block", listener); - return; - } - - try { - const { proposedEvent } = await onNewL2Block( - l2Provider, - blockNumber, - proposer, - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - blocksProposed.push(proposedEvent); - - console.log("proposed", proposedEvent.args.id); - } catch (e) { - hasFailedAssertions = true; - l2Provider.off("block", listener); - console.error(e); - throw e; - } - }; - - l2Provider.on("block", listener); - while (blocksProposed.length < maxNumBlocks.toNumber() - 1) { - await sleep(3 * 1000); - } - - expect(hasFailedAssertions).to.be.eq(false); - - const provedBlocks: BlockInfo[] = []; - - await Promise.all( - blocksProposed.map(async (block) => { + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + console.log("proposed", proposedEvent.args.id); + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + }); + + eventEmitter.on( + BLOCK_PROPOSED_EVENT, + async function ( + proposedEvent: BlockProposedEvent, + blockNumber: number + ) { const proverAddress = await proverSigner.getAddress(); const { args } = await prover.prove( await proverSigner.getAddress(), - block.args.id.toNumber(), - blockIdsToNumber[block.args.id.toString()], - block.args.meta as any as BlockMetadata + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata ); const { blockHash, id: blockId, parentHash, provenAt } = args; const proposedBlock = await taikoL1.getProposedBlock( - block.args.id.toNumber() + proposedEvent.args.id.toNumber() ); console.log("proposed block", proposedBlock); @@ -270,32 +223,25 @@ describe("tokenomics: proofReward", function () { expect(forkChoice.provers[0]).to.be.eq(proverAddress); - provedBlocks.push({ + const provedBlock = { proposedAt: proposedBlock.proposedAt.toNumber(), provenAt: provenAt.toNumber(), - id: block.args.id.toNumber(), + id: proposedEvent.args.id.toNumber(), parentHash: parentHash, blockHash: blockHash, forkChoice: forkChoice, deposit: proposedBlock.deposit, proposer: proposedBlock.proposer, - }); - }) - ); + }; - // wait for all blocks to be proven - /* eslint-disable-next-line */ - while (provedBlocks.length < maxNumBlocks.toNumber() - 1) { - await sleep(3 * 1000); - } + eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + } + ); let lastProofReward: BigNumber = BigNumber.from(0); + let blocksVerified: number = 0; - // now try to verify the blocks and make sure the proof reward shrinks as slots - // free up - for (let i = 0; i < provedBlocks.length; i++) { - const block = provedBlocks[i]; - expect(block).not.to.be.undefined; + eventEmitter.on(BLOCK_PROVEN_EVENT, async function (block: BlockInfo) { console.log("verifying blocks", block); const { newProofReward } = await verifyBlockAndAssert( @@ -303,9 +249,14 @@ describe("tokenomics: proofReward", function () { tkoTokenL1, block, lastProofReward, - i > 0 + block.id > 1 ); lastProofReward = newProofReward; + blocksVerified++; + }); + + while (blocksVerified < maxNumBlocks.toNumber() - 1) { + await sleep(3 * 1000); } }); @@ -316,7 +267,6 @@ describe("tokenomics: proofReward", function () { they receive the proofReward. the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const blockIdsToNumber: any = {}; const proposers = (await createAndSeedWallets(3, l1Signer)).map( (p: ethers.Wallet) => @@ -354,109 +304,92 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); } - let hasFailedAssertions: boolean = false; - const blocksProposed: BlockProposedEvent[] = []; - const listener = async (blockNumber: number) => { + const eventEmitter = new EventEmitter(); + + l2Provider.on("block", async (blockNumber: number) => { if (blockNumber <= genesisHeight) return; - // fill up all slots. - if (blocksProposed.length === maxNumBlocks.toNumber() - 1) { - l2Provider.off("block", listener); - return; - } - try { - const { proposedEvent } = await onNewL2Block( - l2Provider, + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + randEle(proposers), + taikoL1, + proposerSigner, + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + console.log("proposed", proposedEvent.args.id); + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + }); + + eventEmitter.on( + BLOCK_PROPOSED_EVENT, + async function ( + proposedEvent: BlockProposedEvent, + blockNumber: number + ) { + const proverAddress = await proverSigner.getAddress(); + const { args } = await randEle(provers).prove( + await proverSigner.getAddress(), + proposedEvent.args.id.toNumber(), blockNumber, - randEle(proposers), - blockIdsToNumber, - taikoL1, - proposerSigner, - tkoTokenL1 + proposedEvent.args.meta as any as BlockMetadata ); - expect(proposedEvent).not.to.be.undefined; - blocksProposed.push(proposedEvent); - - console.log("proposed", proposedEvent.args.id); - } catch (e) { - hasFailedAssertions = true; - l2Provider.off("block", listener); - console.error(e); - throw e; - } - }; - - l2Provider.on("block", listener); - - while (blocksProposed.length < maxNumBlocks.toNumber() - 1) { - await sleep(3 * 1000); - } - - expect(hasFailedAssertions).to.be.eq(false); - - const provedBlocks: BlockInfo[] = []; - - for (const block of blocksProposed) { - const proverAddress = await proverSigner.getAddress(); - const { args } = await randEle(provers).prove( - await proverSigner.getAddress(), - block.args.id.toNumber(), - blockIdsToNumber[block.args.id.toString()], - block.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - block.args.id.toNumber() - ); + const { blockHash, id: blockId, parentHash, provenAt } = args; - console.log("proposed block", proposedBlock); + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.id.toNumber() + ); - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); - expect(forkChoice.blockHash).to.be.eq(blockHash); + expect(forkChoice.blockHash).to.be.eq(blockHash); - expect(forkChoice.provers[0]).to.be.eq(proverAddress); + expect(forkChoice.provers[0]).to.be.eq(proverAddress); - provedBlocks.push({ - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: block.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }); - } + const provedBlock = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: proposedEvent.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; - // wait for all blocks to be proven - /* eslint-disable-next-line */ - while (provedBlocks.length < maxNumBlocks.toNumber() - 1) { - await sleep(3 * 1000); - } + eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + } + ); let lastProofReward: BigNumber = BigNumber.from(0); - // now try to verify the blocks and make sure the proof reward shrinks as slots - // free up - for (let i = 0; i < provedBlocks.length; i++) { - const block = provedBlocks[i]; - console.log("proving block", block); - expect(block).not.to.be.undefined; + let blocksVerified: number = 0; + eventEmitter.on( + BLOCK_PROVEN_EVENT, + async function (provedBlock: BlockInfo) { + console.log("proving block", provedBlock); - const { newProofReward } = await verifyBlockAndAssert( - taikoL1, - tkoTokenL1, - block, - lastProofReward, - i > 0 - ); - lastProofReward = newProofReward; + const { newProofReward } = await verifyBlockAndAssert( + taikoL1, + tkoTokenL1, + provedBlock, + lastProofReward, + provedBlock.id > 1 + ); + lastProofReward = newProofReward; + blocksVerified++; + } + ); + + while (blocksVerified < maxNumBlocks.toNumber() - 1) { + console.log("blocks verified", blocksVerified); + await sleep(2 * 1000); } }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index d1560e7482..403c1339ea 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -48,7 +48,6 @@ async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, blockNumber: number, proposer: Proposer, - blockIdsToNumber: any, taikoL1: TaikoL1, proposerSigner: any, tkoTokenL1: TkoToken @@ -66,8 +65,6 @@ async function onNewL2Block( const { id, meta } = proposedEvent.args; - blockIdsToNumber[id.toString()] = block.number; - const newProofReward = await taikoL1.getProofReward( new Date().getMilliseconds(), meta.timestamp @@ -277,6 +274,10 @@ function randEle(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } +const BLOCK_PROPOSED_EVENT = "blockProposed"; +const BLOCK_PROVEN_EVENT = "blockProved"; +const BLOCK_VERIFIED_EVENT = "blockVerified"; + export { sendTinyEtherToZeroAddress, onNewL2Block, @@ -284,6 +285,9 @@ export { initTokenomicsFixture, verifyBlockAndAssert, randEle, + BLOCK_PROPOSED_EVENT, + BLOCK_PROVEN_EVENT, + BLOCK_VERIFIED_EVENT, }; export type { BlockInfo, ForkChoice }; From ee8cf239855af935e6b0902bbca38679b753ec98 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 26 Jan 2023 14:46:48 -0800 Subject: [PATCH 21/45] refactor listeners to utils methods --- .../test/tokenomics/proofReward.test.ts | 178 +++--------------- packages/protocol/test/tokenomics/utils.ts | 74 ++++++++ 2 files changed, 100 insertions(+), 152 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 9268e28558..8b4d94e224 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -2,10 +2,7 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import EventEmitter from "events"; import { TaikoL1 } from "../../typechain"; -import { BlockProposedEvent } from "../../typechain/LibProposing"; -import { BlockProvenEvent } from "../../typechain/LibProving"; import { TestTkoToken } from "../../typechain/TestTkoToken"; -import { BlockMetadata } from "../utils/block_metadata"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; import createAndSeedWallets from "../utils/seed"; @@ -16,7 +13,8 @@ import { BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT, initTokenomicsFixture, - onNewL2Block, + newProposerListener, + newProverListener, randEle, sleepUntilBlockIsVerifiable, verifyBlockAndAssert, @@ -62,57 +60,21 @@ describe("tokenomics: proofReward", function () { const prover = new Prover(taikoL1, l2Provider, proverSigner); const eventEmitter = new EventEmitter(); - l2Provider.on("block", async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - - const { proposedEvent } = await onNewL2Block( + l2Provider.on( + "block", + newProposerListener( + genesisHeight, + eventEmitter, l2Provider, - blockNumber, proposer, taikoL1, - proposerSigner, tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); - }); + ) + ); eventEmitter.on( BLOCK_PROPOSED_EVENT, - async ( - proposedBlockEvent: BlockProposedEvent, - blockNumber: number - ) => { - const event: BlockProvenEvent = await prover.prove( - await proverSigner.getAddress(), - proposedBlockEvent.args.id.toNumber(), - blockNumber, - proposedBlockEvent.args.meta as any - ); - - const proposedBlock = await taikoL1.getProposedBlock( - proposedBlockEvent.args.id.toNumber() - ); - - const forkChoice = await taikoL1.getForkChoice( - proposedBlockEvent.args.id.toNumber(), - event.args.parentHash - ); - - const blockInfo = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: event.args.provenAt.toNumber(), - id: event.args.id.toNumber(), - parentHash: event.args.parentHash, - blockHash: event.args.blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, blockInfo); - } + newProverListener(prover, taikoL1, eventEmitter) ); eventEmitter.on( @@ -176,66 +138,21 @@ describe("tokenomics: proofReward", function () { ).wait(1); const eventEmitter = new EventEmitter(); - l2Provider.on("block", async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - - const { proposedEvent } = await onNewL2Block( + l2Provider.on( + "block", + newProposerListener( + genesisHeight, + eventEmitter, l2Provider, - blockNumber, proposer, taikoL1, - proposerSigner, tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - console.log("proposed", proposedEvent.args.id); - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); - }); + ) + ); eventEmitter.on( BLOCK_PROPOSED_EVENT, - async function ( - proposedEvent: BlockProposedEvent, - blockNumber: number - ) { - const proverAddress = await proverSigner.getAddress(); - const { args } = await prover.prove( - await proverSigner.getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - proposedEvent.args.id.toNumber() - ); - - console.log("proposed block", proposedBlock); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq(proverAddress); - - const provedBlock = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: proposedEvent.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); - } + newProverListener(prover, taikoL1, eventEmitter) ); let lastProofReward: BigNumber = BigNumber.from(0); @@ -307,64 +224,21 @@ describe("tokenomics: proofReward", function () { const eventEmitter = new EventEmitter(); - l2Provider.on("block", async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - - const { proposedEvent } = await onNewL2Block( + l2Provider.on( + "block", + newProposerListener( + genesisHeight, + eventEmitter, l2Provider, - blockNumber, randEle(proposers), taikoL1, - proposerSigner, tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - console.log("proposed", proposedEvent.args.id); - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); - }); + ) + ); eventEmitter.on( BLOCK_PROPOSED_EVENT, - async function ( - proposedEvent: BlockProposedEvent, - blockNumber: number - ) { - const proverAddress = await proverSigner.getAddress(); - const { args } = await randEle(provers).prove( - await proverSigner.getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - proposedEvent.args.id.toNumber() - ); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq(proverAddress); - - const provedBlock = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: proposedEvent.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); - } + newProverListener(randEle(provers), taikoL1, eventEmitter) ); let lastProofReward: BigNumber = BigNumber.from(0); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 403c1339ea..2422daca75 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -16,6 +16,9 @@ import { ethers as hardhatEthers } from "hardhat"; import { BlockProposedEvent } from "../../typechain/LibProposing"; import { expect } from "chai"; import verifyBlocks from "../utils/verify"; +import EventEmitter from "events"; +import Prover from "../utils/prover"; +import { BlockMetadata } from "../utils/block_metadata"; type ForkChoice = { provenAt: BigNumber; @@ -274,6 +277,75 @@ function randEle(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } +function newProposerListener( + genesisHeight: number, + eventEmitter: EventEmitter, + l2Provider: ethers.providers.JsonRpcProvider, + proposer: Proposer, + taikoL1: TaikoL1, + tkoTokenL1: TkoToken +) { + return async (blockNumber: number) => { + if (blockNumber <= genesisHeight) return; + + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposer.getSigner(), + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + }; +} + +function newProverListener( + prover: Prover, + taikoL1: TaikoL1, + eventEmitter: EventEmitter +) { + return async (proposedEvent: BlockProposedEvent, blockNumber: number) => { + const { args } = await prover.prove( + await prover.getSigner().getAddress(), + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata + ); + const { blockHash, id: blockId, parentHash, provenAt } = args; + + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.id.toNumber() + ); + + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq( + await prover.getSigner().getAddress() + ); + + const provedBlock = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: proposedEvent.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; + + eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + }; +} + const BLOCK_PROPOSED_EVENT = "blockProposed"; const BLOCK_PROVEN_EVENT = "blockProved"; const BLOCK_VERIFIED_EVENT = "blockVerified"; @@ -285,6 +357,8 @@ export { initTokenomicsFixture, verifyBlockAndAssert, randEle, + newProposerListener, + newProverListener, BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT, BLOCK_VERIFIED_EVENT, From 6fa8eae3ee5de10c22fb534a8b365facb755e4f6 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 26 Jan 2023 15:19:37 -0800 Subject: [PATCH 22/45] bubble up errors from eventhandlers to main thread --- .../test/tokenomics/proofReward.test.ts | 33 +++++- packages/protocol/test/tokenomics/utils.ts | 104 ++++++++++-------- 2 files changed, 85 insertions(+), 52 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 8b4d94e224..2c9b531f26 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -59,7 +59,10 @@ describe("tokenomics: proofReward", function () { const prover = new Prover(taikoL1, l2Provider, proverSigner); + let failedAssertion: Error | null = null; + const eventEmitter = new EventEmitter(); + l2Provider.on( "block", newProposerListener( @@ -77,6 +80,10 @@ describe("tokenomics: proofReward", function () { newProverListener(prover, taikoL1, eventEmitter) ); + eventEmitter.on("error", (e: Error) => (failedAssertion = e)); + + let blocksVerified: number = 0; + eventEmitter.on( BLOCK_PROVEN_EVENT, async function (blockInfo: BlockInfo) { @@ -104,8 +111,17 @@ describe("tokenomics: proofReward", function () { // prover should have given given 1 TKO token, since they // held no TKO balance. expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); + blocksVerified++; } ); + + /* eslint-disable-next-line */ + while (blocksVerified < 1) { + expect(failedAssertion).to.be.null; + await sleep(3 * 1000); + } + + expect(failedAssertion).to.be.null; }); it(`single prover, single proposer. @@ -125,6 +141,11 @@ describe("tokenomics: proofReward", function () { proposerSigner ); + let failedAssertion: Error | null = null; + + const eventEmitter = new EventEmitter(); + eventEmitter.on("error", (e: Error) => (failedAssertion = e)); + const prover = new Prover(taikoL1, l2Provider, proverSigner); // prover needs TKO or their reward will be cut down to 1 wei. @@ -137,7 +158,6 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); - const eventEmitter = new EventEmitter(); l2Provider.on( "block", newProposerListener( @@ -173,8 +193,10 @@ describe("tokenomics: proofReward", function () { }); while (blocksVerified < maxNumBlocks.toNumber() - 1) { + expect(failedAssertion).to.be.null; await sleep(3 * 1000); } + expect(failedAssertion).to.be.null; }); it(`multiple provers, multiple proposers. @@ -201,6 +223,10 @@ describe("tokenomics: proofReward", function () { (p: ethers.Wallet) => new Prover(taikoL1, l2Provider, p) ); + const eventEmitter = new EventEmitter(); + + let failedAssertion: Error | null = null; + eventEmitter.on("error", (e: Error) => (failedAssertion = e)); for (const prover of provers) { await ( await tkoTokenL1 @@ -222,8 +248,6 @@ describe("tokenomics: proofReward", function () { ).wait(1); } - const eventEmitter = new EventEmitter(); - l2Provider.on( "block", newProposerListener( @@ -262,8 +286,9 @@ describe("tokenomics: proofReward", function () { ); while (blocksVerified < maxNumBlocks.toNumber() - 1) { - console.log("blocks verified", blocksVerified); + expect(failedAssertion).to.be.null; await sleep(2 * 1000); } + expect(failedAssertion).to.be.null; }); }); diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index 2422daca75..d3da201df4 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -286,19 +286,23 @@ function newProposerListener( tkoTokenL1: TkoToken ) { return async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - - const { proposedEvent } = await onNewL2Block( - l2Provider, - blockNumber, - proposer, - taikoL1, - proposer.getSigner(), - tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + try { + if (blockNumber <= genesisHeight) return; + + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposer.getSigner(), + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + } catch (e) { + eventEmitter.emit("error", e); + } }; } @@ -308,41 +312,45 @@ function newProverListener( eventEmitter: EventEmitter ) { return async (proposedEvent: BlockProposedEvent, blockNumber: number) => { - const { args } = await prover.prove( - await prover.getSigner().getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - proposedEvent.args.id.toNumber() - ); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq( - await prover.getSigner().getAddress() - ); - - const provedBlock = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: proposedEvent.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + try { + const { args } = await prover.prove( + await prover.getSigner().getAddress(), + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata + ); + const { blockHash, id: blockId, parentHash, provenAt } = args; + + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.id.toNumber() + ); + + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq( + await prover.getSigner().getAddress() + ); + + const provedBlock = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: proposedEvent.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; + + eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + } catch (e) { + eventEmitter.emit("error", e); + } }; } From 07173db5395bda8ed62f225ca9c4b15eb7883b0c Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 27 Jan 2023 11:39:11 -0800 Subject: [PATCH 23/45] refactor tests to root utils folder so we can reuse this code for TaikoL1 integration tests --- .../protocol/test/tokenomics/blockFee.test.ts | 4 +- .../test/tokenomics/proofReward.test.ts | 17 +- packages/protocol/test/tokenomics/utils.ts | 253 +----------------- packages/protocol/test/utils/array.ts | 5 + .../protocol/test/utils/block_metadata.ts | 21 +- packages/protocol/test/utils/event.ts | 5 + packages/protocol/test/utils/onNewL2Block.ts | 48 ++++ packages/protocol/test/utils/propose.ts | 38 ++- packages/protocol/test/utils/prove.ts | 55 +++- packages/protocol/test/utils/verify.ts | 93 ++++++- 10 files changed, 270 insertions(+), 269 deletions(-) create mode 100644 packages/protocol/test/utils/array.ts create mode 100644 packages/protocol/test/utils/event.ts create mode 100644 packages/protocol/test/utils/onNewL2Block.ts diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 3da5e5b522..62ef7fd1af 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -2,11 +2,12 @@ import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import { AddressManager, TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; +import { onNewL2Block } from "../utils/onNewL2Block"; import Proposer from "../utils/proposer"; import sleep from "../utils/sleep"; import { deployTaikoL1 } from "../utils/taikoL1"; -import { initTokenomicsFixture, onNewL2Block } from "./utils"; +import { initTokenomicsFixture } from "./utils"; describe("tokenomics: blockFee", function () { let taikoL1: TaikoL1; @@ -126,6 +127,7 @@ describe("tokenomics: blockFee", function () { await sleep(1 * 1000); } l2Provider.off("block"); + expect(hasFailedAssertions).to.be.eq(false); }); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 2c9b531f26..3e178d0045 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -3,22 +3,21 @@ import { BigNumber, ethers } from "ethers"; import EventEmitter from "events"; import { TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; +import { randEle } from "../utils/array"; +import { BlockInfo } from "../utils/block_metadata"; +import { BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT } from "../utils/event"; +import { newProposerListener } from "../utils/propose"; import Proposer from "../utils/proposer"; +import { newProverListener } from "../utils/prove"; import Prover from "../utils/prover"; import createAndSeedWallets from "../utils/seed"; import sleep from "../utils/sleep"; -import verifyBlocks from "../utils/verify"; import { - BlockInfo, - BLOCK_PROPOSED_EVENT, - BLOCK_PROVEN_EVENT, - initTokenomicsFixture, - newProposerListener, - newProverListener, - randEle, sleepUntilBlockIsVerifiable, verifyBlockAndAssert, -} from "./utils"; + verifyBlocks, +} from "../utils/verify"; +import { initTokenomicsFixture } from "./utils"; describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index d3da201df4..dce5f8389c 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -1,93 +1,15 @@ import { BigNumber, ethers } from "ethers"; -import { TaikoL1, TkoToken } from "../../typechain"; import deployAddressManager from "../utils/addressManager"; -import Proposer from "../utils/proposer"; import { getDefaultL2Signer, getL1Provider, getL2Provider, } from "../utils/provider"; import createAndSeedWallets from "../utils/seed"; -import sleep from "../utils/sleep"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import { ethers as hardhatEthers } from "hardhat"; -import { BlockProposedEvent } from "../../typechain/LibProposing"; -import { expect } from "chai"; -import verifyBlocks from "../utils/verify"; -import EventEmitter from "events"; -import Prover from "../utils/prover"; -import { BlockMetadata } from "../utils/block_metadata"; - -type ForkChoice = { - provenAt: BigNumber; - provers: string[]; - blockHash: string; -}; - -type BlockInfo = { - proposedAt: number; - provenAt: number; - id: number; - parentHash: string; - blockHash: string; - forkChoice: ForkChoice; - deposit: BigNumber; - proposer: string; -}; - -async function sleepUntilBlockIsVerifiable( - taikoL1: TaikoL1, - id: number, - provenAt: number -) { - const delay = await taikoL1.getUncleProofDelay(id); - const delayInMs = delay.mul(1000); - await sleep(5 * delayInMs.toNumber()); // TODO: use provenAt, calc difference, etc -} - -async function onNewL2Block( - l2Provider: ethers.providers.JsonRpcProvider, - blockNumber: number, - proposer: Proposer, - taikoL1: TaikoL1, - proposerSigner: any, - tkoTokenL1: TkoToken -): Promise<{ - proposedEvent: BlockProposedEvent; - newProposerTkoBalance: BigNumber; - newBlockFee: BigNumber; - newProofReward: BigNumber; -}> { - const block = await l2Provider.getBlock(blockNumber); - const receipt = await proposer.commitThenProposeBlock(block); - const proposedEvent: BlockProposedEvent = (receipt.events as any[]).find( - (e) => e.event === "BlockProposed" - ); - - const { id, meta } = proposedEvent.args; - - const newProofReward = await taikoL1.getProofReward( - new Date().getMilliseconds(), - meta.timestamp - ); - - const newProposerTkoBalance = await tkoTokenL1.balanceOf( - await proposerSigner.getAddress() - ); - - const newBlockFee = await taikoL1.getBlockFee(); - - console.log("-------------------proposed----------", id); - - return { - proposedEvent, - newProposerTkoBalance, - newBlockFee, - newProofReward, - }; -} const sendTinyEtherToZeroAddress = async (signer: any) => { await signer @@ -199,177 +121,4 @@ async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { }; } -async function verifyBlockAndAssert( - taikoL1: TaikoL1, - tkoTokenL1: TkoToken, - block: BlockInfo, - lastProofReward: BigNumber, - compareHeaders: boolean = false -): Promise<{ newProofReward: BigNumber }> { - await sleepUntilBlockIsVerifiable(taikoL1, block.id, block.provenAt); - - const isVerifiable = await taikoL1.isBlockVerifiable( - block.id, - block.parentHash - ); - - expect(isVerifiable).to.be.eq(true); - - // dont verify first blocks parent hash, because we arent "real L2" in these - // tests, the parent hash will be wrong. - // if (compareHeaders) { - // const latestHash = await taikoL1.getLatestSyncedHeader(); - // expect(latestHash).to.be.eq(block.parentHash); - // } - const prover = block.forkChoice.provers[0]; - - const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( - prover - ); - - const proposerTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( - block.proposer - ); - - expect(proposerTkoBalanceBeforeVerification.gt(0)).to.be.eq(true); - const verifiedEvent = await verifyBlocks(taikoL1, 1); - expect(verifiedEvent).to.be.not.undefined; - - expect(verifiedEvent.args.blockHash).to.be.eq(block.blockHash); - expect(verifiedEvent.args.id.eq(block.id)).to.be.eq(true); - - const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( - prover - ); - - // prover should have increased in balance as he received the proof reward. - expect( - proverTkoBalanceAfterVerification.gt(proverTkoBalanceBeforeVerification) - ).to.be.eq(true); - - const newProofReward = await taikoL1.getProofReward( - block.proposedAt, - block.provenAt - ); - - // last proof reward should be larger than the new proof reward, - // since we have stopped proposing, and slots are growing as we verify. - if (lastProofReward.gt(0)) { - expect(newProofReward).to.be.lt(lastProofReward); - } - - // latest synced header should be our just-verified block hash. - const latestHash = await taikoL1.getLatestSyncedHeader(); - expect(latestHash).to.be.eq(block.blockHash); - - // fork choice should be nullified via _cleanUp in LibVerifying - const forkChoice = await taikoL1.getForkChoice(block.id, block.parentHash); - expect(forkChoice.provenAt).to.be.eq(BigNumber.from(0)); - expect(forkChoice.provers).to.be.empty; - expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); - - // proposer should be minted their refund of their deposit back after - // verification, as long as their balance is > 0; - return { newProofReward }; -} - -function randEle(arr: T[]): T { - return arr[Math.floor(Math.random() * arr.length)]; -} - -function newProposerListener( - genesisHeight: number, - eventEmitter: EventEmitter, - l2Provider: ethers.providers.JsonRpcProvider, - proposer: Proposer, - taikoL1: TaikoL1, - tkoTokenL1: TkoToken -) { - return async (blockNumber: number) => { - try { - if (blockNumber <= genesisHeight) return; - - const { proposedEvent } = await onNewL2Block( - l2Provider, - blockNumber, - proposer, - taikoL1, - proposer.getSigner(), - tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); - } catch (e) { - eventEmitter.emit("error", e); - } - }; -} - -function newProverListener( - prover: Prover, - taikoL1: TaikoL1, - eventEmitter: EventEmitter -) { - return async (proposedEvent: BlockProposedEvent, blockNumber: number) => { - try { - const { args } = await prover.prove( - await prover.getSigner().getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - proposedEvent.args.id.toNumber() - ); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq( - await prover.getSigner().getAddress() - ); - - const provedBlock = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: proposedEvent.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); - } catch (e) { - eventEmitter.emit("error", e); - } - }; -} - -const BLOCK_PROPOSED_EVENT = "blockProposed"; -const BLOCK_PROVEN_EVENT = "blockProved"; -const BLOCK_VERIFIED_EVENT = "blockVerified"; - -export { - sendTinyEtherToZeroAddress, - onNewL2Block, - sleepUntilBlockIsVerifiable, - initTokenomicsFixture, - verifyBlockAndAssert, - randEle, - newProposerListener, - newProverListener, - BLOCK_PROPOSED_EVENT, - BLOCK_PROVEN_EVENT, - BLOCK_VERIFIED_EVENT, -}; - -export type { BlockInfo, ForkChoice }; +export { initTokenomicsFixture }; diff --git a/packages/protocol/test/utils/array.ts b/packages/protocol/test/utils/array.ts new file mode 100644 index 0000000000..ded2aaff71 --- /dev/null +++ b/packages/protocol/test/utils/array.ts @@ -0,0 +1,5 @@ +function randEle(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +export { randEle }; diff --git a/packages/protocol/test/utils/block_metadata.ts b/packages/protocol/test/utils/block_metadata.ts index 74532a6ebd..c1ee3b2865 100644 --- a/packages/protocol/test/utils/block_metadata.ts +++ b/packages/protocol/test/utils/block_metadata.ts @@ -1,4 +1,4 @@ -import { BigNumberish } from "ethers"; +import { BigNumber, BigNumberish } from "ethers"; type BlockMetadata = { id: number; @@ -14,4 +14,21 @@ type BlockMetadata = { commitHeight: number; }; -export { BlockMetadata }; +type ForkChoice = { + provenAt: BigNumber; + provers: string[]; + blockHash: string; +}; + +type BlockInfo = { + proposedAt: number; + provenAt: number; + id: number; + parentHash: string; + blockHash: string; + forkChoice: ForkChoice; + deposit: BigNumber; + proposer: string; +}; + +export { BlockMetadata, ForkChoice, BlockInfo }; diff --git a/packages/protocol/test/utils/event.ts b/packages/protocol/test/utils/event.ts new file mode 100644 index 0000000000..4115617d16 --- /dev/null +++ b/packages/protocol/test/utils/event.ts @@ -0,0 +1,5 @@ +const BLOCK_PROPOSED_EVENT = "blockProposed"; +const BLOCK_PROVEN_EVENT = "blockProved"; +const BLOCK_VERIFIED_EVENT = "blockVerified"; + +export { BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT, BLOCK_VERIFIED_EVENT }; diff --git a/packages/protocol/test/utils/onNewL2Block.ts b/packages/protocol/test/utils/onNewL2Block.ts new file mode 100644 index 0000000000..b9127fbb37 --- /dev/null +++ b/packages/protocol/test/utils/onNewL2Block.ts @@ -0,0 +1,48 @@ +import { BigNumber, ethers } from "ethers"; +import { TaikoL1, TkoToken } from "../../typechain"; +import { BlockProposedEvent } from "../../typechain/LibProposing"; +import Proposer from "./proposer"; + +async function onNewL2Block( + l2Provider: ethers.providers.JsonRpcProvider, + blockNumber: number, + proposer: Proposer, + taikoL1: TaikoL1, + proposerSigner: any, + tkoTokenL1: TkoToken +): Promise<{ + proposedEvent: BlockProposedEvent; + newProposerTkoBalance: BigNumber; + newBlockFee: BigNumber; + newProofReward: BigNumber; +}> { + const block = await l2Provider.getBlock(blockNumber); + const receipt = await proposer.commitThenProposeBlock(block); + const proposedEvent: BlockProposedEvent = (receipt.events as any[]).find( + (e) => e.event === "BlockProposed" + ); + + const { id, meta } = proposedEvent.args; + + const newProofReward = await taikoL1.getProofReward( + new Date().getMilliseconds(), + meta.timestamp + ); + + const newProposerTkoBalance = await tkoTokenL1.balanceOf( + await proposerSigner.getAddress() + ); + + const newBlockFee = await taikoL1.getBlockFee(); + + console.log("-------------------proposed----------", id); + + return { + proposedEvent, + newProposerTkoBalance, + newBlockFee, + newProofReward, + }; +} + +export { onNewL2Block }; diff --git a/packages/protocol/test/utils/propose.ts b/packages/protocol/test/utils/propose.ts index f5655ccf54..ed4ae208d5 100644 --- a/packages/protocol/test/utils/propose.ts +++ b/packages/protocol/test/utils/propose.ts @@ -1,8 +1,13 @@ +import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import RLP from "rlp"; -import { TaikoL1 } from "../../typechain"; +import { EventEmitter } from "stream"; +import { TaikoL1, TkoToken } from "../../typechain"; import { BlockMetadata } from "./block_metadata"; import { encodeBlockMetadata } from "./encoding"; +import { BLOCK_PROPOSED_EVENT } from "./event"; +import { onNewL2Block } from "./onNewL2Block"; +import Proposer from "./proposer"; const buildProposeBlockInputs = ( block: ethers.providers.Block, @@ -44,4 +49,33 @@ const proposeBlock = async ( return receipt; }; -export { buildProposeBlockInputs, proposeBlock }; +function newProposerListener( + genesisHeight: number, + eventEmitter: EventEmitter, + l2Provider: ethers.providers.JsonRpcProvider, + proposer: Proposer, + taikoL1: TaikoL1, + tkoTokenL1: TkoToken +) { + return async (blockNumber: number) => { + try { + if (blockNumber <= genesisHeight) return; + + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposer.getSigner(), + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); + } catch (e) { + eventEmitter.emit("error", e); + } + }; +} + +export { buildProposeBlockInputs, proposeBlock, newProposerListener }; diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index db4e3df350..22e21e01df 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -1,9 +1,14 @@ +import { expect } from "chai"; import { ethers } from "ethers"; +import { EventEmitter } from "stream"; import { TaikoL1 } from "../../typechain"; +import { BlockProposedEvent } from "../../typechain/LibProposing"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { encodeEvidence } from "./encoding"; +import { BLOCK_PROVEN_EVENT } from "./event"; import Evidence from "./evidence"; +import Prover from "./prover"; import { BlockHeader, getBlockHeader } from "./rpc"; const buildProveBlockInputs = ( @@ -61,4 +66,52 @@ const proveBlock = async ( return event; }; -export { buildProveBlockInputs, proveBlock }; +function newProverListener( + prover: Prover, + taikoL1: TaikoL1, + eventEmitter: EventEmitter +) { + return async (proposedEvent: BlockProposedEvent, blockNumber: number) => { + try { + const { args } = await prover.prove( + await prover.getSigner().getAddress(), + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata + ); + const { blockHash, id: blockId, parentHash, provenAt } = args; + + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.id.toNumber() + ); + + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq( + await prover.getSigner().getAddress() + ); + + const provedBlock = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: proposedEvent.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; + + eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); + } catch (e) { + eventEmitter.emit("error", e); + } + }; +} + +export { buildProveBlockInputs, proveBlock, newProverListener }; diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts index 8b2140cb4c..78db87b8cd 100644 --- a/packages/protocol/test/utils/verify.ts +++ b/packages/protocol/test/utils/verify.ts @@ -1,5 +1,10 @@ -import { TaikoL1 } from "../../typechain"; +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { ethers } from "hardhat"; +import { TaikoL1, TkoToken } from "../../typechain"; import { BlockVerifiedEvent } from "../../typechain/LibVerifying"; +import { BlockInfo } from "./block_metadata"; +import sleep from "./sleep"; async function verifyBlocks(taikoL1: TaikoL1, maxBlocks: number) { const verifyTx = await taikoL1.verifyBlocks(maxBlocks); @@ -10,4 +15,88 @@ async function verifyBlocks(taikoL1: TaikoL1, maxBlocks: number) { return verifiedEvent; } -export default verifyBlocks; +async function sleepUntilBlockIsVerifiable( + taikoL1: TaikoL1, + id: number, + provenAt: number +) { + const delay = await taikoL1.getUncleProofDelay(id); + const delayInMs = delay.mul(1000); + await sleep(5 * delayInMs.toNumber()); // TODO: use provenAt, calc difference, etc +} + +async function verifyBlockAndAssert( + taikoL1: TaikoL1, + tkoTokenL1: TkoToken, + block: BlockInfo, + lastProofReward: BigNumber, + compareHeaders: boolean = false +): Promise<{ newProofReward: BigNumber }> { + await sleepUntilBlockIsVerifiable(taikoL1, block.id, block.provenAt); + + const isVerifiable = await taikoL1.isBlockVerifiable( + block.id, + block.parentHash + ); + + expect(isVerifiable).to.be.eq(true); + + // dont verify first blocks parent hash, because we arent "real L2" in these + // tests, the parent hash will be wrong. + // if (compareHeaders) { + // const latestHash = await taikoL1.getLatestSyncedHeader(); + // expect(latestHash).to.be.eq(block.parentHash); + // } + const prover = block.forkChoice.provers[0]; + + const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + prover + ); + + const proposerTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( + block.proposer + ); + + expect(proposerTkoBalanceBeforeVerification.gt(0)).to.be.eq(true); + const verifiedEvent = await verifyBlocks(taikoL1, 1); + expect(verifiedEvent).to.be.not.undefined; + + expect(verifiedEvent.args.blockHash).to.be.eq(block.blockHash); + expect(verifiedEvent.args.id.eq(block.id)).to.be.eq(true); + + const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf( + prover + ); + + // prover should have increased in balance as he received the proof reward. + expect( + proverTkoBalanceAfterVerification.gt(proverTkoBalanceBeforeVerification) + ).to.be.eq(true); + + const newProofReward = await taikoL1.getProofReward( + block.proposedAt, + block.provenAt + ); + + // last proof reward should be larger than the new proof reward, + // since we have stopped proposing, and slots are growing as we verify. + if (lastProofReward.gt(0)) { + expect(newProofReward).to.be.lt(lastProofReward); + } + + // latest synced header should be our just-verified block hash. + const latestHash = await taikoL1.getLatestSyncedHeader(); + expect(latestHash).to.be.eq(block.blockHash); + + // fork choice should be nullified via _cleanUp in LibVerifying + const forkChoice = await taikoL1.getForkChoice(block.id, block.parentHash); + expect(forkChoice.provenAt).to.be.eq(BigNumber.from(0)); + expect(forkChoice.provers).to.be.empty; + expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); + + // proposer should be minted their refund of their deposit back after + // verification, as long as their balance is > 0; + return { newProofReward }; +} + +export { verifyBlocks, verifyBlockAndAssert, sleepUntilBlockIsVerifiable }; From a5dcb69589f19b52caf819150b184f2e24a0cdec Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Fri, 27 Jan 2023 12:37:23 -0800 Subject: [PATCH 24/45] add taikol1 integration tests and begin refactorign them for more reusable methods --- .../test/L1/TaikoL1.integration.test.ts | 116 ++++++++++-------- .../test/tokenomics/proofReward.test.ts | 2 +- packages/protocol/test/tokenomics/utils.ts | 16 +-- packages/protocol/test/utils/commit.ts | 47 ++++++- packages/protocol/test/utils/seed.ts | 10 +- 5 files changed, 127 insertions(+), 64 deletions(-) diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 38dc69c855..77837217d7 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -4,9 +4,18 @@ import { ethers } from "hardhat"; import { TaikoL1, TaikoL2 } from "../../typechain"; import deployAddressManager from "../utils/addressManager"; import { BlockMetadata } from "../utils/block_metadata"; -import { commitBlock, generateCommitHash } from "../utils/commit"; -import { buildProposeBlockInputs, proposeBlock } from "../utils/propose"; -import { getDefaultL2Signer, getL1Provider } from "../utils/provider"; +import { + commitAndProposeLatestBlock, + commitBlock, + generateCommitHash, +} from "../utils/commit"; +import { buildProposeBlockInputs } from "../utils/propose"; +import { + getDefaultL2Signer, + getL1Provider, + getL2Provider, +} from "../utils/provider"; +import { sendTinyEtherToZeroAddress } from "../utils/seed"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; @@ -14,12 +23,11 @@ describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; let taikoL2: TaikoL2; let l2Provider: ethersLib.providers.JsonRpcProvider; - let l2Signer: ethersLib.Signer; + let l2Signer: any; + let l1Signer: any; beforeEach(async function () { - l2Provider = new ethers.providers.JsonRpcProvider( - "http://localhost:28545" - ); + l2Provider = getL2Provider(); l2Signer = await getDefaultL2Signer(); @@ -33,8 +41,9 @@ describe("integration:TaikoL1", function () { l1Provider.pollingInterval = 100; const signers = await ethers.getSigners(); + l1Signer = signers[0]; - const l1AddressManager = await deployAddressManager(signers[0]); + const l1AddressManager = await deployAddressManager(l1Signer); taikoL1 = await deployTaikoL1( l1AddressManager, @@ -64,6 +73,30 @@ describe("integration:TaikoL1", function () { expect(isCommitValid).to.be.eq(false); }); + + it("should be valid if it has been committed", async function () { + const { commitConfirmations } = await taikoL1.getConfig(); + const block = await l2Provider.getBlock("latest"); + const commitSlot = 0; + const { commit, blockCommittedEvent } = await commitBlock( + taikoL1, + block, + commitSlot + ); + expect(blockCommittedEvent).not.to.be.undefined; + + for (let i = 0; i < commitConfirmations.toNumber(); i++) { + await sendTinyEtherToZeroAddress(l1Signer); + } + + const isCommitValid = await taikoL1.isCommitValid( + commitSlot, + blockCommittedEvent!.blockNumber, + commit.hash + ); + + expect(isCommitValid).to.be.eq(true); + }); }); describe("getProposedBlock()", function () { @@ -72,6 +105,26 @@ describe("integration:TaikoL1", function () { "L1:id" ); }); + + it("should return valid block if it's been commmited and proposed", async function () { + const commitSlot = 0; + const { proposedEvent } = await commitAndProposeLatestBlock( + taikoL1, + l1Signer, + l2Provider, + commitSlot + ); + expect(proposedEvent).not.to.be.undefined; + expect(proposedEvent.args.meta.commitSlot).to.be.eq(commitSlot); + + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.meta.id + ); + expect(proposedBlock).not.to.be.undefined; + expect(proposedBlock.proposer).to.be.eq( + await l1Signer.getAddress() + ); + }); }); describe("commitBlock() -> proposeBlock() integration", async function () { it("should fail if a proposed block's placeholder field values are not default", async function () { @@ -161,26 +214,7 @@ describe("integration:TaikoL1", function () { }); it("should commit and be able to propose", async function () { - const block = await l2Provider.getBlock("latest"); - const commitSlot = 0; - const { tx, commit } = await commitBlock( - taikoL1, - block, - commitSlot - ); - - const { commitConfirmations } = await taikoL1.getConfig(); - - await tx.wait(commitConfirmations.toNumber()); - const receipt = await proposeBlock( - taikoL1, - block, - commit.txListHash, - tx.blockNumber as number, - block.gasLimit, - commitSlot - ); - expect(receipt.status).to.be.eq(1); + await commitAndProposeLatestBlock(taikoL1, l1Signer, l2Provider, 0); const stateVariables = await taikoL1.getStateVariables(); const nextBlockId = stateVariables[4]; @@ -202,20 +236,13 @@ describe("integration:TaikoL1", function () { // propose blocks and fill up maxNumBlocks number of slots, // expect each one to be successful. for (let i = 0; i < maxNumBlocks.toNumber() - 1; i++) { - const block = await l2Provider.getBlock("latest"); - const { tx, commit } = await commitBlock(taikoL1, block, i); - - const receipt = await proposeBlock( + await commitAndProposeLatestBlock( taikoL1, - block, - commit.txListHash, - tx.blockNumber as number, - block.gasLimit, - i + l1Signer, + l2Provider, + 0 ); - expect(receipt.status).to.be.eq(1); - const stateVariables = await taikoL1.getStateVariables(); const nextBlockId = stateVariables[4]; const proposedBlock = await taikoL1.getProposedBlock( @@ -235,17 +262,8 @@ describe("integration:TaikoL1", function () { // now expect another proposed block to be invalid since all slots are full and none have // been proven. - const block = await l2Provider.getBlock("latest"); - const { tx, commit } = await commitBlock(taikoL1, block); - await expect( - proposeBlock( - taikoL1, - block, - commit.txListHash, - tx.blockNumber as number, - block.gasLimit - ) + commitAndProposeLatestBlock(taikoL1, l1Signer, l2Provider) ).to.be.revertedWith("L1:tooMany"); }); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 3e178d0045..ee66c8015c 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -10,7 +10,7 @@ import { newProposerListener } from "../utils/propose"; import Proposer from "../utils/proposer"; import { newProverListener } from "../utils/prove"; import Prover from "../utils/prover"; -import createAndSeedWallets from "../utils/seed"; +import { createAndSeedWallets } from "../utils/seed"; import sleep from "../utils/sleep"; import { sleepUntilBlockIsVerifiable, diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/tokenomics/utils.ts index dce5f8389c..19fe96cf36 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/tokenomics/utils.ts @@ -1,24 +1,18 @@ -import { BigNumber, ethers } from "ethers"; +import { ethers } from "ethers"; import deployAddressManager from "../utils/addressManager"; import { getDefaultL2Signer, getL1Provider, getL2Provider, } from "../utils/provider"; -import createAndSeedWallets from "../utils/seed"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; import deployTkoToken from "../utils/tkoToken"; import { ethers as hardhatEthers } from "hardhat"; - -const sendTinyEtherToZeroAddress = async (signer: any) => { - await signer - .sendTransaction({ - to: ethers.constants.AddressZero, - value: BigNumber.from(1), - }) - .wait(1); -}; +import { + createAndSeedWallets, + sendTinyEtherToZeroAddress, +} from "../utils/seed"; async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { const l1Provider = getL1Provider(); diff --git a/packages/protocol/test/utils/commit.ts b/packages/protocol/test/utils/commit.ts index f22f366c0c..2e69510faa 100644 --- a/packages/protocol/test/utils/commit.ts +++ b/packages/protocol/test/utils/commit.ts @@ -1,6 +1,10 @@ import { ethers } from "ethers"; import RLP from "rlp"; import { TaikoL1 } from "../../typechain"; +import { BlockProposedEvent } from "../../typechain/LibProposing"; +import { BlockCommittedEvent } from "../../typechain/TaikoEvents"; +import { proposeBlock } from "./propose"; +import { sendTinyEtherToZeroAddress } from "./seed"; const generateCommitHash = ( block: ethers.providers.Block @@ -23,10 +27,49 @@ const commitBlock = async ( ): Promise<{ tx: ethers.ContractTransaction; commit: { hash: string; txListHash: string }; + blockCommittedEvent: BlockCommittedEvent | undefined; + receipt: ethers.ContractReceipt; }> => { const commit = generateCommitHash(block); const tx = await taikoL1.commitBlock(commitSlot, commit.hash); - return { tx, commit }; + const receipt = await tx.wait(1); + const blockCommittedEvent = receipt.events!.find( + (e) => e.event === "BlockCommitted" + ) as any as BlockCommittedEvent; + return { tx, commit, blockCommittedEvent, receipt }; }; -export { generateCommitHash, commitBlock }; +const commitAndProposeLatestBlock = async ( + taikoL1: TaikoL1, + l1Signer: any, + l2Provider: ethers.providers.JsonRpcProvider, + commitSlot: number = 0 +) => { + const { commitConfirmations } = await taikoL1.getConfig(); + const block = await l2Provider.getBlock("latest"); + const { tx, commit } = await commitBlock( + taikoL1.connect(l1Signer), + block, + commitSlot + ); + const commitReceipt = await tx.wait(1); + + for (let i = 0; i < commitConfirmations.toNumber(); i++) { + await sendTinyEtherToZeroAddress(l1Signer); + } + + const proposeReceipt = await proposeBlock( + taikoL1.connect(l1Signer), + block, + commit.txListHash, + commitReceipt.blockNumber as number, + block.gasLimit, + commitSlot + ); + const proposedEvent: BlockProposedEvent = ( + proposeReceipt.events as any[] + ).find((e) => e.event === "BlockProposed"); + return { proposedEvent, proposeReceipt, commitReceipt, commit }; +}; + +export { generateCommitHash, commitBlock, commitAndProposeLatestBlock }; diff --git a/packages/protocol/test/utils/seed.ts b/packages/protocol/test/utils/seed.ts index 12b12775ec..ba4431c5fb 100644 --- a/packages/protocol/test/utils/seed.ts +++ b/packages/protocol/test/utils/seed.ts @@ -22,4 +22,12 @@ const createAndSeedWallets = async ( return wallets; }; -export default createAndSeedWallets; +const sendTinyEtherToZeroAddress = async (signer: any) => { + const tx = await signer.sendTransaction({ + to: ethers.constants.AddressZero, + value: BigNumber.from(1), + }); + await tx.wait(1); +}; + +export { createAndSeedWallets, sendTinyEtherToZeroAddress }; From 33c8c28dda81921566a90879608aa4aa0b934355 Mon Sep 17 00:00:00 2001 From: Daniel Wang <99078276+dantaik@users.noreply.github.com> Date: Mon, 30 Jan 2023 14:52:17 +0800 Subject: [PATCH 25/45] Update packages/protocol/test/tokenomics/proofReward.test.ts Co-authored-by: David --- packages/protocol/test/tokenomics/proofReward.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index ee66c8015c..466cf8ce07 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -107,7 +107,7 @@ describe("tokenomics: proofReward", function () { const proverTkoBalanceAfterVerification = await tkoTokenL1.balanceOf(blockInfo.forkChoice.provers[0]); - // prover should have given given 1 TKO token, since they + // prover should have given 1 TKO token, since they // held no TKO balance. expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); blocksVerified++; From 0e7ad12e23fbafbb5d5ecf33cb65c859206cc320 Mon Sep 17 00:00:00 2001 From: Daniel Wang <99078276+dantaik@users.noreply.github.com> Date: Mon, 30 Jan 2023 14:52:33 +0800 Subject: [PATCH 26/45] Update packages/protocol/test/tokenomics/blockFee.test.ts Co-authored-by: David --- packages/protocol/test/tokenomics/blockFee.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 62ef7fd1af..94614c73c6 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -72,7 +72,7 @@ describe("tokenomics: blockFee", function () { proposerSigner ); - // get the initiaal tkoBalance, which should decrease every block proposal + // get the initial tkoBalance, which should decrease every block proposal let lastProposerTkoBalance = await tkoTokenL1.balanceOf( await proposerSigner.getAddress() ); From b849452da2ecee1cb436e172f37f8ec57381bf8c Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 10:47:27 -0800 Subject: [PATCH 27/45] add circuits in to evidence --- packages/protocol/test/tokenomics/proofReward.test.ts | 6 +++++- packages/protocol/test/utils/encoding.ts | 2 +- packages/protocol/test/utils/evidence.ts | 1 + packages/protocol/test/utils/prove.ts | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 466cf8ce07..d70707b5f8 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,3 +1,4 @@ +import { AssertionError } from "assert"; import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import EventEmitter from "events"; @@ -143,7 +144,10 @@ describe("tokenomics: proofReward", function () { let failedAssertion: Error | null = null; const eventEmitter = new EventEmitter(); - eventEmitter.on("error", (e: Error) => (failedAssertion = e)); + eventEmitter.on("error", (e: AssertionError) => { + console.error(e.actual); + failedAssertion = e; + }); const prover = new Prover(taikoL1, l2Provider, proverSigner); diff --git a/packages/protocol/test/utils/encoding.ts b/packages/protocol/test/utils/encoding.ts index 7984bb22e7..4661733e7c 100644 --- a/packages/protocol/test/utils/encoding.ts +++ b/packages/protocol/test/utils/encoding.ts @@ -14,7 +14,7 @@ function encodeBlockMetadata(meta: BlockMetadata) { function encodeEvidence(evidence: Evidence) { return ethers.utils.defaultAbiCoder.encode( [ - "tuple(tuple(uint256 id, uint256 l1Height, bytes32 l1Hash, address beneficiary, bytes32 txListHash, bytes32 mixHash, bytes extraData, uint64 gasLimit, uint64 timestamp, uint64 commitHeight, uint64 commitSlot) meta, tuple(bytes32 parentHash, bytes32 ommersHash, address beneficiary, bytes32 stateRoot, bytes32 transactionsRoot, bytes32 receiptsRoot, bytes32[8] logsBloom, uint256 difficulty, uint128 height, uint64 gasLimit, uint64 gasUsed, uint64 timestamp, bytes extraData, bytes32 mixHash, uint64 nonce, uint256 baseFeePerGas) header, address prover, bytes[] proofs)", + "tuple(tuple(uint256 id, uint256 l1Height, bytes32 l1Hash, address beneficiary, bytes32 txListHash, bytes32 mixHash, bytes extraData, uint64 gasLimit, uint64 timestamp, uint64 commitHeight, uint64 commitSlot) meta, tuple(bytes32 parentHash, bytes32 ommersHash, address beneficiary, bytes32 stateRoot, bytes32 transactionsRoot, bytes32 receiptsRoot, bytes32[8] logsBloom, uint256 difficulty, uint128 height, uint64 gasLimit, uint64 gasUsed, uint64 timestamp, bytes extraData, bytes32 mixHash, uint64 nonce, uint256 baseFeePerGas) header, address prover, bytes[] proofs, uint16[] circuits)", ], [evidence] ); diff --git a/packages/protocol/test/utils/evidence.ts b/packages/protocol/test/utils/evidence.ts index 58e39c1641..2bc68269da 100644 --- a/packages/protocol/test/utils/evidence.ts +++ b/packages/protocol/test/utils/evidence.ts @@ -6,6 +6,7 @@ type Evidence = { header: BlockHeader; prover: string; proofs: string[]; + circuits: number[]; }; export default Evidence; diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index 22e21e01df..cb0abaa00a 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -25,6 +25,7 @@ const buildProveBlockInputs = ( header: header, prover: prover, proofs: [], + circuits: [], }; // we have mkp + zkp returnign true in testing, so can just push 0xff @@ -34,6 +35,10 @@ const buildProveBlockInputs = ( evidence.proofs.push("0xff"); } + for (let i = 0; i < zkProofsPerBlock; i++) { + evidence.circuits.push(1); + } + inputs[0] = encodeEvidence(evidence); inputs[1] = anchorTx; inputs[2] = anchorReceipt; From 3553849822b67a368b61df3763ed0606aada13d3 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 13:54:24 -0800 Subject: [PATCH 28/45] . --- packages/protocol/test/tokenomics/proofReward.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index d70707b5f8..b8fc074520 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,4 +1,3 @@ -import { AssertionError } from "assert"; import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import EventEmitter from "events"; @@ -43,7 +42,10 @@ describe("tokenomics: proofReward", function () { } = await initTokenomicsFixture()); }); - afterEach(() => clearInterval(interval)); + afterEach(() => { + clearInterval(interval); + l2Provider.off("block"); + }); it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); @@ -144,8 +146,7 @@ describe("tokenomics: proofReward", function () { let failedAssertion: Error | null = null; const eventEmitter = new EventEmitter(); - eventEmitter.on("error", (e: AssertionError) => { - console.error(e.actual); + eventEmitter.on("error", (e: Error) => { failedAssertion = e; }); From 9f903102d2d2767b2baf24f6441506afdf77ab5b Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 16:05:23 -0800 Subject: [PATCH 29/45] change logging --- .../test/tokenomics/proofReward.test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index b8fc074520..cb83f8e920 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -81,8 +81,10 @@ describe("tokenomics: proofReward", function () { BLOCK_PROPOSED_EVENT, newProverListener(prover, taikoL1, eventEmitter) ); - - eventEmitter.on("error", (e: Error) => (failedAssertion = e)); + eventEmitter.on("error", (e: Error) => { + console.error(e); + failedAssertion = e; + }); let blocksVerified: number = 0; @@ -147,6 +149,7 @@ describe("tokenomics: proofReward", function () { const eventEmitter = new EventEmitter(); eventEmitter.on("error", (e: Error) => { + console.error(e); failedAssertion = e; }); @@ -183,7 +186,7 @@ describe("tokenomics: proofReward", function () { let blocksVerified: number = 0; eventEmitter.on(BLOCK_PROVEN_EVENT, async function (block: BlockInfo) { - console.log("verifying blocks", block); + console.log("verifying blocks", block.id); const { newProofReward } = await verifyBlockAndAssert( taikoL1, @@ -194,6 +197,7 @@ describe("tokenomics: proofReward", function () { ); lastProofReward = newProofReward; blocksVerified++; + console.log("verified block", block.id); }); while (blocksVerified < maxNumBlocks.toNumber() - 1) { @@ -230,7 +234,10 @@ describe("tokenomics: proofReward", function () { const eventEmitter = new EventEmitter(); let failedAssertion: Error | null = null; - eventEmitter.on("error", (e: Error) => (failedAssertion = e)); + eventEmitter.on("error", (e: Error) => { + console.error(e); + failedAssertion = e; + }); for (const prover of provers) { await ( await tkoTokenL1 @@ -275,7 +282,7 @@ describe("tokenomics: proofReward", function () { eventEmitter.on( BLOCK_PROVEN_EVENT, async function (provedBlock: BlockInfo) { - console.log("proving block", provedBlock); + console.log("verifying", provedBlock.id); const { newProofReward } = await verifyBlockAndAssert( taikoL1, @@ -286,6 +293,8 @@ describe("tokenomics: proofReward", function () { ); lastProofReward = newProofReward; blocksVerified++; + + console.log("verified", provedBlock.id); } ); From 8fd36e553c8fc01ce128c96d5555539e8921648d Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 16:28:44 -0800 Subject: [PATCH 30/45] forkchoice tests --- .../contracts/test/L1/TestTaikoL1.sol | 4 +- .../test/L1/TaikoL1.integration.test.ts | 48 +++++++++++++++++++ packages/protocol/test/utils/commit.ts | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index 7b32e9df66..c920ddf8f7 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -46,10 +46,10 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.blockTimeCap = 48 seconds; config.proofTimeCap = 60 minutes; config.bootstrapDiscountHalvingPeriod = 180 days; - config.initialUncleDelay = 1 minutes; + config.initialUncleDelay = 1 seconds; config.enableTokenomics = false; config.enablePublicInputsCheck = true; - config.enableProofValidation = true; + config.enableProofValidation = false; } function verifyZKP( diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 77837217d7..7315317eb0 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -10,6 +10,7 @@ import { generateCommitHash, } from "../utils/commit"; import { buildProposeBlockInputs } from "../utils/propose"; +import { proveBlock } from "../utils/prove"; import { getDefaultL2Signer, getL1Provider, @@ -58,6 +59,15 @@ describe("integration:TaikoL1", function () { `${l2ChainId}.taiko`, taikoL2.address ); + + const { chainId } = await l1Provider.getNetwork(); + + await ( + await l1AddressManager.setAddress( + `${chainId}.proof_verifier`, + taikoL1.address + ) + ).wait(1); }); describe("isCommitValid()", async function () { @@ -126,6 +136,44 @@ describe("integration:TaikoL1", function () { ); }); }); + + describe("getForkChoice", function () { + it("returns no empty fork choice for un-proposed, un-proven and un-verified block", async function () { + const forkChoice = await taikoL1.getForkChoice( + 1, + ethers.constants.HashZero + ); + expect(forkChoice.blockHash).to.be.eq(ethers.constants.HashZero); + expect(forkChoice.provenAt).to.be.eq(0); + }); + + it("returns populated data for submitted fork choice", async function () { + const { proposedEvent, block } = await commitAndProposeLatestBlock( + taikoL1, + l1Signer, + l2Provider, + 0 + ); + + expect(proposedEvent).not.to.be.undefined; + const proveEvent = await proveBlock( + taikoL1, + l2Provider, + await l1Signer.getAddress(), + proposedEvent.args.id.toNumber(), + block.number, + proposedEvent.args.meta as any as BlockMetadata + ); + expect(proveEvent).not.to.be.undefined; + + const forkChoice = await taikoL1.getForkChoice( + proposedEvent.args.id.toNumber(), + block.parentHash + ); + expect(forkChoice.blockHash).to.be.eq(block.hash); + expect(forkChoice.provers[0]).to.be.eq(await l1Signer.getAddress()); + }); + }); describe("commitBlock() -> proposeBlock() integration", async function () { it("should fail if a proposed block's placeholder field values are not default", async function () { const block = await l2Provider.getBlock("latest"); diff --git a/packages/protocol/test/utils/commit.ts b/packages/protocol/test/utils/commit.ts index 2e69510faa..4ccfb65aaf 100644 --- a/packages/protocol/test/utils/commit.ts +++ b/packages/protocol/test/utils/commit.ts @@ -69,7 +69,7 @@ const commitAndProposeLatestBlock = async ( const proposedEvent: BlockProposedEvent = ( proposeReceipt.events as any[] ).find((e) => e.event === "BlockProposed"); - return { proposedEvent, proposeReceipt, commitReceipt, commit }; + return { proposedEvent, proposeReceipt, commitReceipt, commit, block }; }; export { generateCommitHash, commitBlock, commitAndProposeLatestBlock }; From b3f6e5141f1bd6472d407d0f209142f76623f2bc Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 16:47:30 -0800 Subject: [PATCH 31/45] small change --- packages/protocol/test/tokenomics/proofReward.test.ts | 9 ++++++--- packages/protocol/test/utils/propose.ts | 9 +++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index cb83f8e920..664f4254f7 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -73,7 +73,8 @@ describe("tokenomics: proofReward", function () { l2Provider, proposer, taikoL1, - tkoTokenL1 + tkoTokenL1, + maxNumBlocks.toNumber() ) ); @@ -173,7 +174,8 @@ describe("tokenomics: proofReward", function () { l2Provider, proposer, taikoL1, - tkoTokenL1 + tkoTokenL1, + maxNumBlocks.toNumber() ) ); @@ -267,7 +269,8 @@ describe("tokenomics: proofReward", function () { l2Provider, randEle(proposers), taikoL1, - tkoTokenL1 + tkoTokenL1, + maxNumBlocks.toNumber() ) ); diff --git a/packages/protocol/test/utils/propose.ts b/packages/protocol/test/utils/propose.ts index ed4ae208d5..75a3f700ec 100644 --- a/packages/protocol/test/utils/propose.ts +++ b/packages/protocol/test/utils/propose.ts @@ -55,11 +55,16 @@ function newProposerListener( l2Provider: ethers.providers.JsonRpcProvider, proposer: Proposer, taikoL1: TaikoL1, - tkoTokenL1: TkoToken + tkoTokenL1: TkoToken, + maxNumBlocks: number ) { return async (blockNumber: number) => { try { - if (blockNumber <= genesisHeight) return; + if ( + blockNumber <= genesisHeight || + blockNumber > genesisHeight + maxNumBlocks + ) + return; const { proposedEvent } = await onNewL2Block( l2Provider, From bf3d85f79cf487ab2bfb01fc2c929689dc1c0866 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 17:09:48 -0800 Subject: [PATCH 32/45] add channels, much cleaner than events i think? --- .../contracts/test/L1/TestTaikoL1.sol | 6 +- packages/protocol/package.json | 3 +- .../test/L1/TaikoL1.integration.test.ts | 61 +++++++++++++++++++ pnpm-lock.yaml | 42 ++++++++----- 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index c920ddf8f7..55198c4f08 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -19,11 +19,11 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.chainId = 167; // up to 2048 pending blocks config.maxNumBlocks = 4; - config.blockHashHistory = 3; + config.blockHashHistory = 1; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 config.zkProofsPerBlock = 1; - config.maxVerificationsPerTx = 2; + config.maxVerificationsPerTx = 0; config.commitConfirmations = 1; config.maxProofsPerForkChoice = 5; config.blockMaxGasLimit = 30000000; // TODO @@ -48,7 +48,7 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.bootstrapDiscountHalvingPeriod = 180 days; config.initialUncleDelay = 1 seconds; config.enableTokenomics = false; - config.enablePublicInputsCheck = true; + config.enablePublicInputsCheck = false; config.enableProofValidation = false; } diff --git a/packages/protocol/package.json b/packages/protocol/package.json index cbbda8a2bc..ca1c057ded 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -82,6 +82,7 @@ "dependencies": { "@gnosis.pm/zodiac": "^1.0.7", "@openzeppelin/contracts": "^4.5.0", - "@openzeppelin/contracts-upgradeable": "^4.5.1" + "@openzeppelin/contracts-upgradeable": "^4.5.1", + "channel-ts": "^0.1.2" } } diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 7315317eb0..ee313e25c9 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -19,6 +19,8 @@ import { import { sendTinyEtherToZeroAddress } from "../utils/seed"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; +import { SimpleChannel } from "channel-ts"; +import { sleepUntilBlockIsVerifiable, verifyBlocks } from "../utils/verify"; describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; @@ -70,6 +72,65 @@ describe("integration:TaikoL1", function () { ).wait(1); }); + describe("getLatestSyncedHeader", function () { + it.only("iterates through a round of blockHashHistory and is still able to get latestSyncedHeader", async function () { + const { blockHashHistory, maxNumBlocks } = + await taikoL1.getConfig(); + const chan = new SimpleChannel(); + + let numBlocksVerified: number = 0; + let blocks: number = 0; + /* eslint-disable-next-line */ + l2Provider.on("block", function (blockNumber: number) { + if (blocks >= maxNumBlocks.toNumber()) { + chan.close(); + l2Provider.off("block"); + return; + } + blocks++; + chan.send(blockNumber); + }); + + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + if (numBlocksVerified > blockHashHistory.toNumber() + 1) { + return; + } + const { proposedEvent } = await commitAndProposeLatestBlock( + taikoL1, + l1Signer, + l2Provider, + 0 + ); + + const provenEvent = await proveBlock( + taikoL1, + l2Provider, + await l1Signer.getAddress(), + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata + ); + + await sleepUntilBlockIsVerifiable( + taikoL1, + proposedEvent.args.id.toNumber(), + provenEvent.args.provenAt.toNumber() + ); + + const verifiedEvent = await verifyBlocks(taikoL1, 1); + expect(verifiedEvent).not.to.be.undefined; + + const latestSyncedHeader = + await taikoL1.getLatestSyncedHeader(); + expect(latestSyncedHeader).not.to.be.eq( + ethers.constants.HashZero + ); + numBlocksVerified++; + } + }); + }); + describe("isCommitValid()", async function () { it("should not be valid if it has not been committed", async function () { const block = await l2Provider.getBlock("latest"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 408e9f9301..0b7fb6edf8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,7 @@ importers: '@typescript-eslint/parser': ^4.33.0 chai: ^4.2.0 chalk: 4.1.2 + channel-ts: ^0.1.2 dotenv: ^10.0.0 eslint: ^7.32.0 eslint-config-prettier: ^8.3.0 @@ -167,15 +168,16 @@ importers: '@gnosis.pm/zodiac': 1.1.9 '@openzeppelin/contracts': 4.8.0 '@openzeppelin/contracts-upgradeable': 4.8.0 + channel-ts: 0.1.2 devDependencies: - '@defi-wonderland/smock': 2.3.4_z67mwh6jmxdufyguqgtbmr6rba + '@defi-wonderland/smock': 2.3.4_d44p6lx7t3c2oeudc2zxbd5d54 '@nomicfoundation/hardhat-network-helpers': 1.0.6_hardhat@2.12.2 '@nomiclabs/hardhat-ethers': 2.2.1_3uaf6nt3qt6cyh5fx3fc5i4mn4 '@nomiclabs/hardhat-etherscan': 3.1.2_hardhat@2.12.2 '@nomiclabs/hardhat-waffle': 2.0.3_wzgqb2xh4egjkdzuv52bfmbi24 '@openzeppelin/hardhat-upgrades': 1.21.0_43thxqwvphwusq7frx7sx2tjf4 - '@typechain/ethers-v5': 7.2.0_67wzblpxnm7h4zc6jlzarjupbm - '@typechain/hardhat': 2.3.1_kqlws2vjqn6dp4diaajzvmozky + '@typechain/ethers-v5': 7.2.0_rqgsou2umo6ifhsmtcb3krjbfm + '@typechain/hardhat': 2.3.1_i3sbpo4srhduw7xvdsryjfwk5e '@types/chai': 4.3.4 '@types/glob': 8.0.1 '@types/mocha': 9.1.1 @@ -197,7 +199,7 @@ importers: glob: 8.1.0 hardhat: 2.12.2_2dtigtkb225m7ii7q45utxqwgi hardhat-abi-exporter: 2.10.1_hardhat@2.12.2 - hardhat-docgen: 1.3.0_hardhat@2.12.2 + hardhat-docgen: 1.3.0_lfwxduevmqdtswpjkbv2koayie hardhat-gas-reporter: 1.0.9_hardhat@2.12.2 lint-staged: 12.5.0 merkle-patricia-tree: 4.2.4 @@ -1580,7 +1582,7 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@defi-wonderland/smock/2.3.4_z67mwh6jmxdufyguqgtbmr6rba: + /@defi-wonderland/smock/2.3.4_d44p6lx7t3c2oeudc2zxbd5d54: resolution: {integrity: sha512-VYJbsoCOdFRyGkAwvaQhQRrU6V8AjK3five8xdbo41DEE9n3qXzUNBUxyD9HhXB/dWWPFWT21IGw5Ztl6Qw3Ew==} peerDependencies: '@ethersproject/abi': ^5 @@ -1590,6 +1592,9 @@ packages: ethers: ^5 hardhat: ^2 dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-evm': 1.0.0 '@nomicfoundation/ethereumjs-util': 8.0.0 '@nomicfoundation/ethereumjs-vm': 6.0.0 @@ -3439,7 +3444,7 @@ packages: typechain: 3.0.0_typescript@4.9.3 dev: true - /@typechain/ethers-v5/7.2.0_67wzblpxnm7h4zc6jlzarjupbm: + /@typechain/ethers-v5/7.2.0_rqgsou2umo6ifhsmtcb3krjbfm: resolution: {integrity: sha512-jfcmlTvaaJjng63QsT49MT6R1HFhtO/TBMWbyzPFSzMmVIqb2tL6prnKBs4ZJrSvmgIXWy+ttSjpaxCTq8D/Tw==} peerDependencies: '@ethersproject/abi': ^5.0.0 @@ -3449,6 +3454,9 @@ packages: typechain: ^5.0.0 typescript: '>=4.0.0' dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/providers': 5.7.2 ethers: 5.7.2 lodash: 4.17.21 ts-essentials: 7.0.3_typescript@4.9.3 @@ -3456,7 +3464,7 @@ packages: typescript: 4.9.3 dev: true - /@typechain/hardhat/2.3.1_kqlws2vjqn6dp4diaajzvmozky: + /@typechain/hardhat/2.3.1_i3sbpo4srhduw7xvdsryjfwk5e: resolution: {integrity: sha512-BQV8OKQi0KAzLXCdsPO0pZBNQQ6ra8A2ucC26uFX/kquRBtJu1yEyWnVSmtr07b5hyRoJRpzUeINLnyqz4/MAw==} peerDependencies: hardhat: ^2.0.10 @@ -3465,6 +3473,7 @@ packages: dependencies: fs-extra: 9.1.0 hardhat: 2.12.2_2dtigtkb225m7ii7q45utxqwgi + lodash: 4.17.21 typechain: 5.2.0_typescript@4.9.3 dev: true @@ -4116,10 +4125,10 @@ packages: source-map: 0.6.1 dev: true - /@vue/component-compiler-utils/3.3.0: + /@vue/component-compiler-utils/3.3.0_lodash@4.17.21: resolution: {integrity: sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==} dependencies: - consolidate: 0.15.1 + consolidate: 0.15.1_lodash@4.17.21 hash-sum: 1.0.2 lru-cache: 4.1.5 merge-source-map: 1.1.0 @@ -6521,6 +6530,10 @@ packages: supports-color: 7.2.0 dev: true + /channel-ts/0.1.2: + resolution: {integrity: sha512-cI/XiDF+jB0v95Xup8xlM7k93lT3xwPl0WdjEZ9w9aUMf5N+3GQevspK2EDYfMyxcKcXdN1F6PDpuYRpUfaZmg==} + dev: false + /char-regex/1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -6940,7 +6953,7 @@ packages: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: true - /consolidate/0.15.1: + /consolidate/0.15.1_lodash@4.17.21: resolution: {integrity: sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==} engines: {node: '>= 0.10.0'} peerDependencies: @@ -7106,6 +7119,7 @@ packages: optional: true dependencies: bluebird: 3.7.2 + lodash: 4.17.21 dev: true /content-disposition/0.5.4: @@ -10426,7 +10440,7 @@ packages: hardhat: 2.12.2_2dtigtkb225m7ii7q45utxqwgi dev: true - /hardhat-docgen/1.3.0_hardhat@2.12.2: + /hardhat-docgen/1.3.0_lfwxduevmqdtswpjkbv2koayie: resolution: {integrity: sha512-paaiOHjJFLCLz2/qM1TQ7ZEG+Vy+LBvJL+SW4A64ZhBnVnyoZ/zv9DvEuawaWhqP5P7AOM6r22reVz4ecWgW7A==} engines: {node: '>=14.14.0'} peerDependencies: @@ -10436,7 +10450,7 @@ packages: hardhat: 2.12.2_2dtigtkb225m7ii7q45utxqwgi html-webpack-plugin: 5.5.0_webpack@5.75.0 vue: 2.7.14 - vue-loader: 15.10.1_z2a3irye3y56jaq776fqxbsiea + vue-loader: 15.10.1_36bk4haztx5nmdoe37hieqy74q vue-router: 3.6.5_vue@2.7.14 vue-template-compiler: 2.7.14 webpack: 5.75.0 @@ -19141,7 +19155,7 @@ packages: resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==} dev: true - /vue-loader/15.10.1_z2a3irye3y56jaq776fqxbsiea: + /vue-loader/15.10.1_36bk4haztx5nmdoe37hieqy74q: resolution: {integrity: sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==} peerDependencies: '@vue/compiler-sfc': ^3.0.8 @@ -19157,7 +19171,7 @@ packages: vue-template-compiler: optional: true dependencies: - '@vue/component-compiler-utils': 3.3.0 + '@vue/component-compiler-utils': 3.3.0_lodash@4.17.21 css-loader: 6.7.2_webpack@5.75.0 hash-sum: 1.0.2 loader-utils: 1.4.2 From f301a447e6d5911024ddc2ba361e9bab44692d24 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 18:06:14 -0800 Subject: [PATCH 33/45] simplify tests and refactor to use channels --- .../contracts/test/L1/TestTaikoL1.sol | 2 +- .../test/L1/TaikoL1.integration.test.ts | 61 ----- .../test/tokenomics/proofReward.test.ts | 230 +++++++----------- packages/protocol/test/utils/blockListener.ts | 24 ++ packages/protocol/test/utils/event.ts | 5 - packages/protocol/test/utils/propose.ts | 43 +--- packages/protocol/test/utils/prove.ts | 55 +---- packages/protocol/test/utils/verify.ts | 91 ++++++- 8 files changed, 201 insertions(+), 310 deletions(-) create mode 100644 packages/protocol/test/utils/blockListener.ts delete mode 100644 packages/protocol/test/utils/event.ts diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index 55198c4f08..70f5900579 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -44,7 +44,7 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.feeGracePeriodPctg = 125; // 125% config.feeMaxPeriodPctg = 375; // 375% config.blockTimeCap = 48 seconds; - config.proofTimeCap = 60 minutes; + config.proofTimeCap = 4 seconds; config.bootstrapDiscountHalvingPeriod = 180 days; config.initialUncleDelay = 1 seconds; config.enableTokenomics = false; diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index ee313e25c9..7315317eb0 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -19,8 +19,6 @@ import { import { sendTinyEtherToZeroAddress } from "../utils/seed"; import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; import { deployTaikoL2 } from "../utils/taikoL2"; -import { SimpleChannel } from "channel-ts"; -import { sleepUntilBlockIsVerifiable, verifyBlocks } from "../utils/verify"; describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; @@ -72,65 +70,6 @@ describe("integration:TaikoL1", function () { ).wait(1); }); - describe("getLatestSyncedHeader", function () { - it.only("iterates through a round of blockHashHistory and is still able to get latestSyncedHeader", async function () { - const { blockHashHistory, maxNumBlocks } = - await taikoL1.getConfig(); - const chan = new SimpleChannel(); - - let numBlocksVerified: number = 0; - let blocks: number = 0; - /* eslint-disable-next-line */ - l2Provider.on("block", function (blockNumber: number) { - if (blocks >= maxNumBlocks.toNumber()) { - chan.close(); - l2Provider.off("block"); - return; - } - blocks++; - chan.send(blockNumber); - }); - - /* eslint-disable-next-line */ - for await (const blockNumber of chan) { - if (numBlocksVerified > blockHashHistory.toNumber() + 1) { - return; - } - const { proposedEvent } = await commitAndProposeLatestBlock( - taikoL1, - l1Signer, - l2Provider, - 0 - ); - - const provenEvent = await proveBlock( - taikoL1, - l2Provider, - await l1Signer.getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - - await sleepUntilBlockIsVerifiable( - taikoL1, - proposedEvent.args.id.toNumber(), - provenEvent.args.provenAt.toNumber() - ); - - const verifiedEvent = await verifyBlocks(taikoL1, 1); - expect(verifiedEvent).not.to.be.undefined; - - const latestSyncedHeader = - await taikoL1.getLatestSyncedHeader(); - expect(latestSyncedHeader).not.to.be.eq( - ethers.constants.HashZero - ); - numBlocksVerified++; - } - }); - }); - describe("isCommitValid()", async function () { it("should not be valid if it has not been committed", async function () { const block = await l2Provider.getBlock("latest"); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 664f4254f7..66b62ab394 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -1,22 +1,14 @@ import { expect } from "chai"; -import { BigNumber, ethers } from "ethers"; -import EventEmitter from "events"; +import { SimpleChannel } from "channel-ts"; +import { ethers } from "ethers"; import { TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import { randEle } from "../utils/array"; -import { BlockInfo } from "../utils/block_metadata"; -import { BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT } from "../utils/event"; -import { newProposerListener } from "../utils/propose"; +import blockListener from "../utils/blockListener"; import Proposer from "../utils/proposer"; -import { newProverListener } from "../utils/prove"; import Prover from "../utils/prover"; import { createAndSeedWallets } from "../utils/seed"; -import sleep from "../utils/sleep"; -import { - sleepUntilBlockIsVerifiable, - verifyBlockAndAssert, - verifyBlocks, -} from "../utils/verify"; +import { commitProposeProveAndVerify } from "../utils/verify"; import { initTokenomicsFixture } from "./utils"; describe("tokenomics: proofReward", function () { @@ -28,6 +20,7 @@ describe("tokenomics: proofReward", function () { let genesisHeight: number; let tkoTokenL1: TestTkoToken; let interval: any; + let chan: SimpleChannel; beforeEach(async () => { ({ @@ -40,11 +33,13 @@ describe("tokenomics: proofReward", function () { tkoTokenL1, interval, } = await initTokenomicsFixture()); + chan = new SimpleChannel(); }); afterEach(() => { clearInterval(interval); l2Provider.off("block"); + chan.close(); }); it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { @@ -60,73 +55,41 @@ describe("tokenomics: proofReward", function () { ); const prover = new Prover(taikoL1, l2Provider, proverSigner); + let proposed: boolean = false; + l2Provider.on("block", function (blockNumber: number) { + if (proposed) { + chan.close(); + l2Provider.off("block"); + return; + } + proposed = true; - let failedAssertion: Error | null = null; + chan.send(blockNumber); + }); - const eventEmitter = new EventEmitter(); + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + const proverTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); - l2Provider.on( - "block", - newProposerListener( - genesisHeight, - eventEmitter, + await commitProposeProveAndVerify( + taikoL1, l2Provider, + blockNumber, proposer, - taikoL1, tkoTokenL1, - maxNumBlocks.toNumber() - ) - ); - - eventEmitter.on( - BLOCK_PROPOSED_EVENT, - newProverListener(prover, taikoL1, eventEmitter) - ); - eventEmitter.on("error", (e: Error) => { - console.error(e); - failedAssertion = e; - }); - - let blocksVerified: number = 0; - - eventEmitter.on( - BLOCK_PROVEN_EVENT, - async function (blockInfo: BlockInfo) { - // make sure block is verifiable before we processe - await sleepUntilBlockIsVerifiable( - taikoL1, - blockInfo.id, - blockInfo.provenAt - ); - - const isVerifiable = await taikoL1.isBlockVerifiable( - blockInfo.id, - blockInfo.parentHash - ); - expect(isVerifiable).to.be.eq(true); - const proverTkoBalanceBeforeVerification = - await tkoTokenL1.balanceOf(blockInfo.forkChoice.provers[0]); - expect(proverTkoBalanceBeforeVerification.eq(0)).to.be.eq(true); - - await verifyBlocks(taikoL1, 1); - - const proverTkoBalanceAfterVerification = - await tkoTokenL1.balanceOf(blockInfo.forkChoice.provers[0]); + prover + ); - // prover should have given 1 TKO token, since they - // held no TKO balance. - expect(proverTkoBalanceAfterVerification.eq(1)).to.be.eq(true); - blocksVerified++; - } - ); + const proverTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); - /* eslint-disable-next-line */ - while (blocksVerified < 1) { - expect(failedAssertion).to.be.null; - await sleep(3 * 1000); + // prover should have given 1 TKO token, since they + // held no TKO balance. + expect(proverTkoBalanceAfterVerification.sub(1)).to.be.eq( + proverTkoBalanceBeforeVerification + ); } - - expect(failedAssertion).to.be.null; }); it(`single prover, single proposer. @@ -146,14 +109,6 @@ describe("tokenomics: proofReward", function () { proposerSigner ); - let failedAssertion: Error | null = null; - - const eventEmitter = new EventEmitter(); - eventEmitter.on("error", (e: Error) => { - console.error(e); - failedAssertion = e; - }); - const prover = new Prover(taikoL1, l2Provider, proverSigner); // prover needs TKO or their reward will be cut down to 1 wei. @@ -168,45 +123,37 @@ describe("tokenomics: proofReward", function () { l2Provider.on( "block", - newProposerListener( + blockListener( + chan, genesisHeight, - eventEmitter, l2Provider, - proposer, - taikoL1, - tkoTokenL1, maxNumBlocks.toNumber() ) ); - eventEmitter.on( - BLOCK_PROPOSED_EVENT, - newProverListener(prover, taikoL1, eventEmitter) - ); - - let lastProofReward: BigNumber = BigNumber.from(0); - let blocksVerified: number = 0; - - eventEmitter.on(BLOCK_PROVEN_EVENT, async function (block: BlockInfo) { - console.log("verifying blocks", block.id); + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + const proverTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); - const { newProofReward } = await verifyBlockAndAssert( + await commitProposeProveAndVerify( taikoL1, + l2Provider, + blockNumber, + proposer, tkoTokenL1, - block, - lastProofReward, - block.id > 1 + prover ); - lastProofReward = newProofReward; - blocksVerified++; - console.log("verified block", block.id); - }); - while (blocksVerified < maxNumBlocks.toNumber() - 1) { - expect(failedAssertion).to.be.null; - await sleep(3 * 1000); + const proverTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); + + expect( + proverTkoBalanceAfterVerification.gt( + proverTkoBalanceBeforeVerification + ) + ).to.be.eq(true); } - expect(failedAssertion).to.be.null; }); it(`multiple provers, multiple proposers. @@ -233,13 +180,6 @@ describe("tokenomics: proofReward", function () { (p: ethers.Wallet) => new Prover(taikoL1, l2Provider, p) ); - const eventEmitter = new EventEmitter(); - - let failedAssertion: Error | null = null; - eventEmitter.on("error", (e: Error) => { - console.error(e); - failedAssertion = e; - }); for (const prover of provers) { await ( await tkoTokenL1 @@ -261,50 +201,50 @@ describe("tokenomics: proofReward", function () { ).wait(1); } + // prover needs TKO or their reward will be cut down to 1 wei. + await ( + await tkoTokenL1 + .connect(l1Signer) + .mintAnyone( + await proverSigner.getAddress(), + ethers.utils.parseEther("100") + ) + ).wait(1); + l2Provider.on( "block", - newProposerListener( + blockListener( + chan, genesisHeight, - eventEmitter, l2Provider, - randEle(proposers), - taikoL1, - tkoTokenL1, maxNumBlocks.toNumber() ) ); - eventEmitter.on( - BLOCK_PROPOSED_EVENT, - newProverListener(randEle(provers), taikoL1, eventEmitter) - ); - - let lastProofReward: BigNumber = BigNumber.from(0); - - let blocksVerified: number = 0; - eventEmitter.on( - BLOCK_PROVEN_EVENT, - async function (provedBlock: BlockInfo) { - console.log("verifying", provedBlock.id); + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + const prover = randEle(provers); + const proposer = randEle(proposers); + const proverTkoBalanceBeforeVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); - const { newProofReward } = await verifyBlockAndAssert( - taikoL1, - tkoTokenL1, - provedBlock, - lastProofReward, - provedBlock.id > 1 - ); - lastProofReward = newProofReward; - blocksVerified++; + await commitProposeProveAndVerify( + taikoL1, + l2Provider, + blockNumber, + proposer, + tkoTokenL1, + prover + ); - console.log("verified", provedBlock.id); - } - ); + const proverTkoBalanceAfterVerification = + await tkoTokenL1.balanceOf(await prover.getSigner().address); - while (blocksVerified < maxNumBlocks.toNumber() - 1) { - expect(failedAssertion).to.be.null; - await sleep(2 * 1000); + expect( + proverTkoBalanceAfterVerification.gt( + proverTkoBalanceBeforeVerification + ) + ).to.be.eq(true); } - expect(failedAssertion).to.be.null; }); }); diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts new file mode 100644 index 0000000000..1de0ed2d28 --- /dev/null +++ b/packages/protocol/test/utils/blockListener.ts @@ -0,0 +1,24 @@ +import { SimpleChannel } from "channel-ts"; +import { ethers } from "ethers"; + +const blockListener = function ( + chan: SimpleChannel, + genesisHeight: number, + l2Provider: ethers.providers.JsonRpcProvider, + maxNumBlocks: number +) { + return function (blockNumber: number) { + if ( + blockNumber < genesisHeight || + blockNumber > genesisHeight + maxNumBlocks + ) { + chan.close(); + l2Provider.off("block"); + return; + } + + chan.send(blockNumber); + }; +}; + +export default blockListener; diff --git a/packages/protocol/test/utils/event.ts b/packages/protocol/test/utils/event.ts deleted file mode 100644 index 4115617d16..0000000000 --- a/packages/protocol/test/utils/event.ts +++ /dev/null @@ -1,5 +0,0 @@ -const BLOCK_PROPOSED_EVENT = "blockProposed"; -const BLOCK_PROVEN_EVENT = "blockProved"; -const BLOCK_VERIFIED_EVENT = "blockVerified"; - -export { BLOCK_PROPOSED_EVENT, BLOCK_PROVEN_EVENT, BLOCK_VERIFIED_EVENT }; diff --git a/packages/protocol/test/utils/propose.ts b/packages/protocol/test/utils/propose.ts index 75a3f700ec..f5655ccf54 100644 --- a/packages/protocol/test/utils/propose.ts +++ b/packages/protocol/test/utils/propose.ts @@ -1,13 +1,8 @@ -import { expect } from "chai"; import { BigNumber, ethers } from "ethers"; import RLP from "rlp"; -import { EventEmitter } from "stream"; -import { TaikoL1, TkoToken } from "../../typechain"; +import { TaikoL1 } from "../../typechain"; import { BlockMetadata } from "./block_metadata"; import { encodeBlockMetadata } from "./encoding"; -import { BLOCK_PROPOSED_EVENT } from "./event"; -import { onNewL2Block } from "./onNewL2Block"; -import Proposer from "./proposer"; const buildProposeBlockInputs = ( block: ethers.providers.Block, @@ -49,38 +44,4 @@ const proposeBlock = async ( return receipt; }; -function newProposerListener( - genesisHeight: number, - eventEmitter: EventEmitter, - l2Provider: ethers.providers.JsonRpcProvider, - proposer: Proposer, - taikoL1: TaikoL1, - tkoTokenL1: TkoToken, - maxNumBlocks: number -) { - return async (blockNumber: number) => { - try { - if ( - blockNumber <= genesisHeight || - blockNumber > genesisHeight + maxNumBlocks - ) - return; - - const { proposedEvent } = await onNewL2Block( - l2Provider, - blockNumber, - proposer, - taikoL1, - proposer.getSigner(), - tkoTokenL1 - ); - expect(proposedEvent).not.to.be.undefined; - - eventEmitter.emit(BLOCK_PROPOSED_EVENT, proposedEvent, blockNumber); - } catch (e) { - eventEmitter.emit("error", e); - } - }; -} - -export { buildProposeBlockInputs, proposeBlock, newProposerListener }; +export { buildProposeBlockInputs, proposeBlock }; diff --git a/packages/protocol/test/utils/prove.ts b/packages/protocol/test/utils/prove.ts index cb0abaa00a..86805455fd 100644 --- a/packages/protocol/test/utils/prove.ts +++ b/packages/protocol/test/utils/prove.ts @@ -1,14 +1,9 @@ -import { expect } from "chai"; import { ethers } from "ethers"; -import { EventEmitter } from "stream"; import { TaikoL1 } from "../../typechain"; -import { BlockProposedEvent } from "../../typechain/LibProposing"; import { BlockProvenEvent } from "../../typechain/LibProving"; import { BlockMetadata } from "./block_metadata"; import { encodeEvidence } from "./encoding"; -import { BLOCK_PROVEN_EVENT } from "./event"; import Evidence from "./evidence"; -import Prover from "./prover"; import { BlockHeader, getBlockHeader } from "./rpc"; const buildProveBlockInputs = ( @@ -71,52 +66,4 @@ const proveBlock = async ( return event; }; -function newProverListener( - prover: Prover, - taikoL1: TaikoL1, - eventEmitter: EventEmitter -) { - return async (proposedEvent: BlockProposedEvent, blockNumber: number) => { - try { - const { args } = await prover.prove( - await prover.getSigner().getAddress(), - proposedEvent.args.id.toNumber(), - blockNumber, - proposedEvent.args.meta as any as BlockMetadata - ); - const { blockHash, id: blockId, parentHash, provenAt } = args; - - const proposedBlock = await taikoL1.getProposedBlock( - proposedEvent.args.id.toNumber() - ); - - const forkChoice = await taikoL1.getForkChoice( - blockId.toNumber(), - parentHash - ); - - expect(forkChoice.blockHash).to.be.eq(blockHash); - - expect(forkChoice.provers[0]).to.be.eq( - await prover.getSigner().getAddress() - ); - - const provedBlock = { - proposedAt: proposedBlock.proposedAt.toNumber(), - provenAt: provenAt.toNumber(), - id: proposedEvent.args.id.toNumber(), - parentHash: parentHash, - blockHash: blockHash, - forkChoice: forkChoice, - deposit: proposedBlock.deposit, - proposer: proposedBlock.proposer, - }; - - eventEmitter.emit(BLOCK_PROVEN_EVENT, provedBlock); - } catch (e) { - eventEmitter.emit("error", e); - } - }; -} - -export { buildProveBlockInputs, proveBlock, newProverListener }; +export { buildProveBlockInputs, proveBlock }; diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts index 78db87b8cd..ed005466cb 100644 --- a/packages/protocol/test/utils/verify.ts +++ b/packages/protocol/test/utils/verify.ts @@ -1,9 +1,12 @@ import { expect } from "chai"; -import { BigNumber } from "ethers"; +import { BigNumber, ethers as ethersLib } from "ethers"; import { ethers } from "hardhat"; import { TaikoL1, TkoToken } from "../../typechain"; import { BlockVerifiedEvent } from "../../typechain/LibVerifying"; -import { BlockInfo } from "./block_metadata"; +import { BlockInfo, BlockMetadata } from "./block_metadata"; +import { onNewL2Block } from "./onNewL2Block"; +import Proposer from "./proposer"; +import Prover from "./prover"; import sleep from "./sleep"; async function verifyBlocks(taikoL1: TaikoL1, maxBlocks: number) { @@ -99,4 +102,86 @@ async function verifyBlockAndAssert( return { newProofReward }; } -export { verifyBlocks, verifyBlockAndAssert, sleepUntilBlockIsVerifiable }; +async function commitProposeProveAndVerify( + taikoL1: TaikoL1, + l2Provider: ethersLib.providers.JsonRpcProvider, + blockNumber: number, + proposer: Proposer, + tkoTokenL1: TkoToken, + prover: Prover +) { + console.log("proposing", blockNumber); + const { proposedEvent } = await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposer.getSigner(), + tkoTokenL1 + ); + expect(proposedEvent).not.to.be.undefined; + + console.log("proving", blockNumber); + const provedEvent = await prover.prove( + await prover.getSigner().getAddress(), + proposedEvent.args.id.toNumber(), + blockNumber, + proposedEvent.args.meta as any as BlockMetadata + ); + + const { args } = provedEvent; + const { blockHash, id: blockId, parentHash, provenAt } = args; + + const proposedBlock = await taikoL1.getProposedBlock( + proposedEvent.args.id.toNumber() + ); + + const forkChoice = await taikoL1.getForkChoice( + blockId.toNumber(), + parentHash + ); + + expect(forkChoice.blockHash).to.be.eq(blockHash); + + expect(forkChoice.provers[0]).to.be.eq( + await prover.getSigner().getAddress() + ); + + const blockInfo = { + proposedAt: proposedBlock.proposedAt.toNumber(), + provenAt: provenAt.toNumber(), + id: proposedEvent.args.id.toNumber(), + parentHash: parentHash, + blockHash: blockHash, + forkChoice: forkChoice, + deposit: proposedBlock.deposit, + proposer: proposedBlock.proposer, + }; + + // make sure block is verifiable before we processe + await sleepUntilBlockIsVerifiable( + taikoL1, + blockInfo.id, + blockInfo.provenAt + ); + + const isVerifiable = await taikoL1.isBlockVerifiable( + blockInfo.id, + blockInfo.parentHash + ); + expect(isVerifiable).to.be.eq(true); + + console.log("verifying", blockNumber); + const verifyEvent = await verifyBlocks(taikoL1, 1); + expect(verifyEvent).not.to.be.eq(undefined); + console.log("verified", blockNumber); + + return { verifyEvent, proposedEvent, provedEvent, proposedBlock }; +} + +export { + verifyBlocks, + verifyBlockAndAssert, + sleepUntilBlockIsVerifiable, + commitProposeProveAndVerify, +}; From 5c90d63b0f868a7121aed3f33b4dbc2633b91124 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 30 Jan 2023 18:11:18 -0800 Subject: [PATCH 34/45] fix taikoL1 test for uncle delay --- packages/protocol/test/L1/TaikoL1.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test/L1/TaikoL1.test.ts b/packages/protocol/test/L1/TaikoL1.test.ts index dbc0a63438..6ec06952af 100644 --- a/packages/protocol/test/L1/TaikoL1.test.ts +++ b/packages/protocol/test/L1/TaikoL1.test.ts @@ -93,7 +93,7 @@ describe("TaikoL1", function () { const constants = await taikoL1.getConfig(); const maxNumBlocks = constants[1]; const delay = await taikoL1.getUncleProofDelay(maxNumBlocks.mul(2)); - const initialUncleDelay = 60; + const initialUncleDelay = 1; expect(delay).to.be.eq(initialUncleDelay); }); From 0c85966aeb00da1f30ac6a647159c4c5bff04c8b Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 1 Feb 2023 12:05:27 -0800 Subject: [PATCH 35/45] blockfee uses channel too --- .../protocol/test/tokenomics/blockFee.test.ts | 73 +++++++++---------- .../test/tokenomics/proofReward.test.ts | 28 ++++--- packages/protocol/test/utils/blockListener.ts | 2 +- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 94614c73c6..3055eb5005 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -1,7 +1,9 @@ import { expect } from "chai"; +import { SimpleChannel } from "channel-ts"; import { BigNumber, ethers } from "ethers"; import { AddressManager, TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; +import blockListener from "../utils/blockListener"; import { onNewL2Block } from "../utils/onNewL2Block"; import Proposer from "../utils/proposer"; @@ -18,6 +20,7 @@ describe("tokenomics: blockFee", function () { let tkoTokenL1: TestTkoToken; let l1AddressManager: AddressManager; let interval: any; + let chan: SimpleChannel; beforeEach(async () => { ({ @@ -30,6 +33,7 @@ describe("tokenomics: blockFee", function () { l1AddressManager, interval, } = await initTokenomicsFixture()); + chan = new SimpleChannel(); }); afterEach(() => clearInterval(interval)); @@ -89,45 +93,36 @@ describe("tokenomics: blockFee", function () { let lastProofReward = BigNumber.from(0); - let hasFailedAssertions: boolean = false; - let blocksProposed: number = 0; - // every time a l2 block is created, we should try to propose it on L1. - l2Provider.on("block", async (blockNumber: number) => { - if (blockNumber <= genesisHeight) return; - try { - const { newProposerTkoBalance, newBlockFee, newProofReward } = - await onNewL2Block( - l2Provider, - blockNumber, - proposer, - taikoL1, - proposerSigner, - tkoTokenL1 - ); - - expect( - newProposerTkoBalance.lt(lastProposerTkoBalance) - ).to.be.eq(true); - expect(newBlockFee.gt(lastBlockFee)).to.be.eq(true); - expect(newProofReward.gt(lastProofReward)).to.be.eq(true); - - lastBlockFee = newBlockFee; - lastProofReward = newProofReward; - lastProposerTkoBalance = newProposerTkoBalance; - blocksProposed++; - } catch (e) { - hasFailedAssertions = true; - throw e; - } - }); - - // wait until one roudn of blocks are proposed, then expect none of them to have - // failed their assertions. - while (blocksProposed < maxNumBlocks.toNumber() - 1) { - await sleep(1 * 1000); + l2Provider.on( + "block", + blockListener( + chan, + genesisHeight, + l2Provider, + maxNumBlocks.toNumber() + ) + ); + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + const { newProposerTkoBalance, newBlockFee, newProofReward } = + await onNewL2Block( + l2Provider, + blockNumber, + proposer, + taikoL1, + proposerSigner, + tkoTokenL1 + ); + + expect(newProposerTkoBalance.lt(lastProposerTkoBalance)).to.be.eq( + true + ); + expect(newBlockFee.gt(lastBlockFee)).to.be.eq(true); + expect(newProofReward.gt(lastProofReward)).to.be.eq(true); + + lastBlockFee = newBlockFee; + lastProofReward = newProofReward; + lastProposerTkoBalance = newProposerTkoBalance; } - l2Provider.off("block"); - - expect(hasFailedAssertions).to.be.eq(false); }); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 66b62ab394..909d3e30e8 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -225,8 +225,13 @@ describe("tokenomics: proofReward", function () { for await (const blockNumber of chan) { const prover = randEle(provers); const proposer = randEle(proposers); - const proverTkoBalanceBeforeVerification = - await tkoTokenL1.balanceOf(await prover.getSigner().address); + const proverTkoBalanceBefore = await tkoTokenL1.balanceOf( + await prover.getSigner().getAddress() + ); + + const proposerTkoBalanceBefore = await tkoTokenL1.balanceOf( + await proposer.getSigner().getAddress() + ); await commitProposeProveAndVerify( taikoL1, @@ -237,14 +242,19 @@ describe("tokenomics: proofReward", function () { prover ); - const proverTkoBalanceAfterVerification = - await tkoTokenL1.balanceOf(await prover.getSigner().address); + const proverTkoBalanceAfter = await tkoTokenL1.balanceOf( + await prover.getSigner().getAddress() + ); - expect( - proverTkoBalanceAfterVerification.gt( - proverTkoBalanceBeforeVerification - ) - ).to.be.eq(true); + const proposerTkoBalanceAfter = await tkoTokenL1.balanceOf( + await proposer.getSigner().getAddress() + ); + + expect(proposerTkoBalanceAfter.lt(proposerTkoBalanceBefore)); + + expect(proverTkoBalanceAfter.gt(proverTkoBalanceBefore)).to.be.eq( + true + ); } }); }); diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index 1de0ed2d28..f85bf73ed2 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -1,4 +1,4 @@ -import { SimpleChannel } from "channel-ts"; +import type { SimpleChannel } from "channel-ts"; import { ethers } from "ethers"; const blockListener = function ( From bc2ff334d50d1e2f824e9a338c31797c0ff660ed Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 1 Feb 2023 12:51:25 -0800 Subject: [PATCH 36/45] libproving + blockfee --- .../protocol/contracts/L1/libs/LibProving.sol | 14 +- .../contracts/test/libs/TestLibProving.sol | 392 ++++++++++++++++++ packages/protocol/test/utils/blockListener.ts | 6 +- packages/protocol/test/utils/taikoL1.ts | 6 +- 4 files changed, 403 insertions(+), 15 deletions(-) create mode 100644 packages/protocol/contracts/test/libs/TestLibProving.sol diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 9f5b2ad5e0..62b366ced3 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -241,14 +241,12 @@ library LibProving { require(evidence.meta.id == target.id, "L1:height"); require(evidence.prover != address(0), "L1:prover"); - if (config.enableProofValidation) { - _checkMetadata({state: state, config: config, meta: target}); - _validateHeaderForMetadata({ - config: config, - header: evidence.header, - meta: evidence.meta - }); - } + _checkMetadata({state: state, config: config, meta: target}); + _validateHeaderForMetadata({ + config: config, + header: evidence.header, + meta: evidence.meta + }); // For alpha-2 testnet, the network allows any address to submit ZKP, // but a special prover can skip ZKP verification if the ZKP is empty. diff --git a/packages/protocol/contracts/test/libs/TestLibProving.sol b/packages/protocol/contracts/test/libs/TestLibProving.sol new file mode 100644 index 0000000000..a4a84171d4 --- /dev/null +++ b/packages/protocol/contracts/test/libs/TestLibProving.sol @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.9; + +import "../../L1/libs/LibProving.sol"; +import "../../common/AddressResolver.sol"; +import "../../libs/LibAnchorSignature.sol"; +import "../../libs/LibBlockHeader.sol"; +import "../../libs/LibReceiptDecoder.sol"; +import "../../libs/LibTxDecoder.sol"; +import "../../libs/LibTxUtils.sol"; +import "../../thirdparty/LibBytesUtils.sol"; +import "../../thirdparty/LibRLPWriter.sol"; +import "../../L1/libs/LibUtils.sol"; + +library TestLibProving { + using LibBlockHeader for BlockHeader; + using LibUtils for TaikoData.BlockMetadata; + using LibUtils for TaikoData.State; + + struct Evidence { + TaikoData.BlockMetadata meta; + BlockHeader header; + address prover; + bytes[] proofs; // The first zkProofsPerBlock are ZKPs, + // followed by MKPs. + uint16[] circuits; // The circuits IDs (size === zkProofsPerBlock) + } + + bytes32 public constant INVALIDATE_BLOCK_LOG_TOPIC = + keccak256("BlockInvalidated(bytes32)"); + + bytes4 public constant ANCHOR_TX_SELECTOR = + bytes4(keccak256("anchor(uint256,bytes32)")); + + event BlockProven( + uint256 indexed id, + bytes32 parentHash, + bytes32 blockHash, + uint64 timestamp, + uint64 provenAt, + address prover + ); + + function proveBlock( + TaikoData.State storage state, + TaikoData.Config memory config, + AddressResolver resolver, + uint256 blockId, + bytes[] calldata inputs + ) public { + assert(!LibUtils.isHalted(state)); + + // Check and decode inputs + require(inputs.length == 3, "L1:inputs:size"); + Evidence memory evidence = abi.decode(inputs[0], (Evidence)); + + bytes calldata anchorTx = inputs[1]; + bytes calldata anchorReceipt = inputs[2]; + + // Check evidence + require(evidence.meta.id == blockId, "L1:id"); + + uint256 zkProofsPerBlock = config.zkProofsPerBlock; + require( + evidence.proofs.length == 2 + zkProofsPerBlock, + "L1:proof:size" + ); + require( + evidence.circuits.length == zkProofsPerBlock, + "L1:circuits:size" + ); + + IProofVerifier proofVerifier = IProofVerifier( + resolver.resolve("proof_verifier", false) + ); + + if (config.enableProofValidation) { + // Check anchor tx is valid + LibTxDecoder.Tx memory _tx = LibTxDecoder.decodeTx( + config.chainId, + anchorTx + ); + require(_tx.txType == 0, "L1:anchor:type"); + require( + _tx.destination == + resolver.resolve(config.chainId, "taiko", false), + "L1:anchor:dest" + ); + require( + _tx.gasLimit == config.anchorTxGasLimit, + "L1:anchor:gasLimit" + ); + + // Check anchor tx's signature is valid and deterministic + _validateAnchorTxSignature(config.chainId, _tx); + + // Check anchor tx's calldata is valid + require( + LibBytesUtils.equal( + _tx.data, + bytes.concat( + ANCHOR_TX_SELECTOR, + bytes32(evidence.meta.l1Height), + evidence.meta.l1Hash + ) + ), + "L1:anchor:calldata" + ); + + // Check anchor tx is the 1st tx in the block + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorTx, + proof: evidence.proofs[zkProofsPerBlock], + root: evidence.header.transactionsRoot + }), + "L1:tx:proof" + ); + + // Check anchor tx does not throw + + LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder + .decodeReceipt(anchorReceipt); + + require(receipt.status == 1, "L1:receipt:status"); + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorReceipt, + proof: evidence.proofs[zkProofsPerBlock + 1], + root: evidence.header.receiptsRoot + }), + "L1:receipt:proof" + ); + } + + // ZK-prove block and mark block proven to be valid. + _proveBlock({ + state: state, + config: config, + resolver: resolver, + proofVerifier: proofVerifier, + evidence: evidence, + target: evidence.meta, + blockHashOverride: 0 + }); + } + + function proveBlockInvalid( + TaikoData.State storage state, + TaikoData.Config memory config, + AddressResolver resolver, + uint256 blockId, + bytes[] calldata inputs + ) public { + assert(!LibUtils.isHalted(state)); + + // Check and decode inputs + require(inputs.length == 3, "L1:inputs:size"); + Evidence memory evidence = abi.decode(inputs[0], (Evidence)); + TaikoData.BlockMetadata memory target = abi.decode( + inputs[1], + (TaikoData.BlockMetadata) + ); + bytes calldata invalidateBlockReceipt = inputs[2]; + + // Check evidence + require(evidence.meta.id == blockId, "L1:id"); + require( + evidence.proofs.length == 1 + config.zkProofsPerBlock, + "L1:proof:size" + ); + + IProofVerifier proofVerifier = IProofVerifier( + resolver.resolve("proof_verifier", false) + ); + + // Check the event is the first one in the throw-away block + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: invalidateBlockReceipt, + proof: evidence.proofs[config.zkProofsPerBlock], + root: evidence.header.receiptsRoot + }), + "L1:receipt:proof" + ); + + // Check the 1st receipt is for an InvalidateBlock tx with + // a BlockInvalidated event + LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder + .decodeReceipt(invalidateBlockReceipt); + require(receipt.status == 1, "L1:receipt:status"); + require(receipt.logs.length == 1, "L1:receipt:logsize"); + + { + LibReceiptDecoder.Log memory log = receipt.logs[0]; + require( + log.contractAddress == + resolver.resolve(config.chainId, "taiko", false), + "L1:receipt:addr" + ); + require(log.data.length == 0, "L1:receipt:data"); + require( + log.topics.length == 2 && + log.topics[0] == INVALIDATE_BLOCK_LOG_TOPIC && + log.topics[1] == target.txListHash, + "L1:receipt:topics" + ); + } + + // ZK-prove block and mark block proven as invalid. + _proveBlock({ + state: state, + config: config, + resolver: resolver, + proofVerifier: proofVerifier, + evidence: evidence, + target: target, + blockHashOverride: LibUtils.BLOCK_DEADEND_HASH + }); + } + + function _proveBlock( + TaikoData.State storage state, + TaikoData.Config memory config, + AddressResolver resolver, + IProofVerifier proofVerifier, + Evidence memory evidence, + TaikoData.BlockMetadata memory target, + bytes32 blockHashOverride + ) private { + require(evidence.meta.id == target.id, "L1:height"); + require(evidence.prover != address(0), "L1:prover"); + + _checkMetadata({state: state, config: config, meta: target}); + _validateHeaderForMetadata({ + config: config, + header: evidence.header, + meta: evidence.meta + }); + + // For alpha-2 testnet, the network allows any address to submit ZKP, + // but a special prover can skip ZKP verification if the ZKP is empty. + + bool skipZKPVerification; + + // TODO(daniel): remove this special address. + if (config.enableOracleProver) { + bytes32 _blockHash = state + .forkChoices[target.id][evidence.header.parentHash].blockHash; + + if (msg.sender == resolver.resolve("oracle_prover", false)) { + require(_blockHash == 0, "L1:mustBeFirstProver"); + skipZKPVerification = true; + } else { + require(_blockHash != 0, "L1:mustNotBeFirstProver"); + } + } + + bytes32 blockHash = evidence.header.hashBlockHeader(); + + if (!skipZKPVerification) { + for (uint256 i = 0; i < config.zkProofsPerBlock; ++i) { + require( + proofVerifier.verifyZKP({ + verifierId: string( + abi.encodePacked( + "plonk_verifier_", + i, + "_", + evidence.circuits[i] + ) + ), + zkproof: evidence.proofs[i], + blockHash: blockHash, + prover: evidence.prover, + txListHash: evidence.meta.txListHash + }), + "L1:zkp" + ); + } + } + + _markBlockProven({ + state: state, + config: config, + prover: evidence.prover, + target: target, + parentHash: evidence.header.parentHash, + blockHash: blockHashOverride == 0 ? blockHash : blockHashOverride + }); + } + + function _markBlockProven( + TaikoData.State storage state, + TaikoData.Config memory config, + address prover, + TaikoData.BlockMetadata memory target, + bytes32 parentHash, + bytes32 blockHash + ) private { + TaikoData.ForkChoice storage fc = state.forkChoices[target.id][ + parentHash + ]; + + if (fc.blockHash == 0) { + // This is the first proof for this block. + fc.blockHash = blockHash; + + if (!config.enableOracleProver) { + // If the oracle prover is not enabled + // we use the first prover's timestamp + fc.provenAt = uint64(block.timestamp); + } else { + // We keep fc.provenAt as 0. + } + } else { + require( + fc.provers.length < config.maxProofsPerForkChoice, + "L1:proof:tooMany" + ); + + require( + fc.provenAt == 0 || + block.timestamp < + LibUtils.getUncleProofDeadline({ + state: state, + config: config, + fc: fc, + blockId: target.id + }), + "L1:tooLate" + ); + + for (uint256 i = 0; i < fc.provers.length; ++i) { + require(fc.provers[i] != prover, "L1:prover:dup"); + } + + if (fc.blockHash != blockHash) { + // We have a problem here: two proofs are both valid but claims + // the new block has different hashes. + if (config.enableOracleProver) { + revert("L1:proof:conflict"); + } else { + LibUtils.halt(state, true); + return; + } + } + + if (config.enableOracleProver && fc.provenAt == 0) { + // If the oracle prover is enabled, we + // use the second prover's timestamp. + fc.provenAt = uint64(block.timestamp); + } + } + + fc.provers.push(prover); + + emit BlockProven({ + id: target.id, + parentHash: parentHash, + blockHash: blockHash, + timestamp: target.timestamp, + provenAt: fc.provenAt, + prover: prover + }); + } + + function _validateAnchorTxSignature( + uint256 chainId, + LibTxDecoder.Tx memory _tx + ) private view {} + + function _checkMetadata( + TaikoData.State storage state, + TaikoData.Config memory config, + TaikoData.BlockMetadata memory meta + ) private view {} + + function _validateHeaderForMetadata( + TaikoData.Config memory config, + BlockHeader memory header, + TaikoData.BlockMetadata memory meta + ) private pure {} +} diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index f85bf73ed2..c3dbb9e1ff 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -8,10 +8,8 @@ const blockListener = function ( maxNumBlocks: number ) { return function (blockNumber: number) { - if ( - blockNumber < genesisHeight || - blockNumber > genesisHeight + maxNumBlocks - ) { + if (blockNumber < genesisHeight) return; + if (blockNumber > genesisHeight + (maxNumBlocks - 1)) { chan.close(); l2Provider.off("block"); return; diff --git a/packages/protocol/test/utils/taikoL1.ts b/packages/protocol/test/utils/taikoL1.ts index d49be70774..c5ded6ffca 100644 --- a/packages/protocol/test/utils/taikoL1.ts +++ b/packages/protocol/test/utils/taikoL1.ts @@ -22,8 +22,8 @@ async function deployTaikoL1( await ethers.getContractFactory("LibProposing") ).deploy(); - const libProving = await ( - await ethers.getContractFactory("LibProving", { + const testLibProving = await ( + await ethers.getContractFactory("TestLibProving", { libraries: { LibReceiptDecoder: libReceiptDecoder.address, LibTxDecoder: libTxDecoder.address, @@ -42,7 +42,7 @@ async function deployTaikoL1( libraries: { LibVerifying: libVerifying.address, LibProposing: libProposing.address, - LibProving: libProving.address, + LibProving: testLibProving.address, }, } ) From c3a56e698e6ecb61d669d39520eed88f01623695 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 10:36:02 -0800 Subject: [PATCH 37/45] comments --- packages/protocol/contracts/test/libs/TestLibProving.sol | 8 ++++++++ packages/protocol/test/tokenomics/proofReward.test.ts | 6 +++--- packages/protocol/test/utils/array.ts | 4 ++-- packages/protocol/test/utils/blockListener.ts | 9 +++++++++ packages/protocol/test/utils/onNewL2Block.ts | 5 +++++ packages/protocol/test/utils/proposer.ts | 2 +- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/protocol/contracts/test/libs/TestLibProving.sol b/packages/protocol/contracts/test/libs/TestLibProving.sol index a4a84171d4..54ffc2d83a 100644 --- a/packages/protocol/contracts/test/libs/TestLibProving.sol +++ b/packages/protocol/contracts/test/libs/TestLibProving.sol @@ -4,6 +4,14 @@ // | |/ _` | | / / _ \ | |__/ _` | '_ (_-< // |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +// This file is an exact copy of LibProving.sol except the implementation of the following methods are empty: + +// _validateAnchorTxSignature +// _checkMetadata +// _validateHeaderForMetadata + +// @dev we need to update this when we update LibProving.sol + pragma solidity ^0.8.9; import "../../L1/libs/LibProving.sol"; diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 909d3e30e8..e3951152ac 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -3,7 +3,7 @@ import { SimpleChannel } from "channel-ts"; import { ethers } from "ethers"; import { TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; -import { randEle } from "../utils/array"; +import { pickRandomElement } from "../utils/array"; import blockListener from "../utils/blockListener"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; @@ -223,8 +223,8 @@ describe("tokenomics: proofReward", function () { /* eslint-disable-next-line */ for await (const blockNumber of chan) { - const prover = randEle(provers); - const proposer = randEle(proposers); + const prover = pickRandomElement(provers); + const proposer = pickRandomElement(proposers); const proverTkoBalanceBefore = await tkoTokenL1.balanceOf( await prover.getSigner().getAddress() ); diff --git a/packages/protocol/test/utils/array.ts b/packages/protocol/test/utils/array.ts index ded2aaff71..3455de9d2f 100644 --- a/packages/protocol/test/utils/array.ts +++ b/packages/protocol/test/utils/array.ts @@ -1,5 +1,5 @@ -function randEle(arr: T[]): T { +function pickRandomElement(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } -export { randEle }; +export { pickRandomElement }; diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index c3dbb9e1ff..adf4b51afc 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -1,6 +1,15 @@ import type { SimpleChannel } from "channel-ts"; import { ethers } from "ethers"; +// blockListener should be called as follows: +// `l2Provider.on("block", blockListener(chan, genesisHeight, l2Provider, maxNumBlocks))` +// it will send incoming blockNumbers, generated from the l2 provider on a new block, +// through a Golang-style channel, which can then be waited on like such: +// for await (const blockNumber of chan) +// so we can then run a commit, propose, prove, and verify flow in our test cases +// in the main javascript event loop, instead of in the event handler of the l2Provider +// itself. + const blockListener = function ( chan: SimpleChannel, genesisHeight: number, diff --git a/packages/protocol/test/utils/onNewL2Block.ts b/packages/protocol/test/utils/onNewL2Block.ts index b9127fbb37..1ebd6ffe8a 100644 --- a/packages/protocol/test/utils/onNewL2Block.ts +++ b/packages/protocol/test/utils/onNewL2Block.ts @@ -3,6 +3,11 @@ import { TaikoL1, TkoToken } from "../../typechain"; import { BlockProposedEvent } from "../../typechain/LibProposing"; import Proposer from "./proposer"; +// onNewL2Block should be called from a tokenomics test case when a new block +// is generated from the l2Provider. +// It will commit then propose the block to the TaikoL1 contract, +// and then return the latest fee information and the proposal event, +// which can then be asserted on depending on your test case. async function onNewL2Block( l2Provider: ethers.providers.JsonRpcProvider, blockNumber: number, diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index f8b919bfb1..cd1cfb84ba 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -36,7 +36,7 @@ class Proposer { async commitThenProposeBlock(block?: ethers.providers.Block) { while (this.proposingMutex) { - await sleep(3 * 1000); + await sleep(500); } this.proposingMutex = true; if (!block) block = await this.l2Provider.getBlock("latest"); From 88aa015963007ef2d463492cb740108d1be77af6 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 10:47:52 -0800 Subject: [PATCH 38/45] try finally in proposer --- packages/protocol/test/utils/proposer.ts | 48 +++++++++++++----------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index cd1cfb84ba..fc74286dd3 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -35,31 +35,35 @@ class Proposer { } async commitThenProposeBlock(block?: ethers.providers.Block) { - while (this.proposingMutex) { - await sleep(500); - } - this.proposingMutex = true; - if (!block) block = await this.l2Provider.getBlock("latest"); - const commitSlot = this.nextCommitSlot++; - const { tx, commit } = await commitBlock( - this.taikoL1, - block, - commitSlot - ); - const commitReceipt = await tx.wait(this.commitConfirms ?? 1); + try { + while (this.proposingMutex) { + await sleep(500); + } + this.proposingMutex = true; + if (!block) block = await this.l2Provider.getBlock("latest"); + const commitSlot = this.nextCommitSlot++; + const { tx, commit } = await commitBlock( + this.taikoL1, + block, + commitSlot + ); + const commitReceipt = await tx.wait(this.commitConfirms ?? 1); - const receipt = await proposeBlock( - this.taikoL1, - block, - commit.txListHash, - commitReceipt.blockNumber as number, - block.gasLimit, - commitSlot - ); + const receipt = await proposeBlock( + this.taikoL1, + block, + commit.txListHash, + commitReceipt.blockNumber as number, + block.gasLimit, + commitSlot + ); - this.proposingMutex = false; + this.proposingMutex = false; - return receipt; + return receipt; + } finally { + this.proposingMutex = false; + } } } From 08ef465da0978cb9534b3e4cceb3065265f5f538 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 12:02:26 -0800 Subject: [PATCH 39/45] reusable integration fixture, test for latestSyncedHeader bug regression we encountered --- .../contracts/test/L1/TestTaikoL1.sol | 2 +- .../test/L1/TaikoL1.integration.test.ts | 134 +++++++++++------- .../protocol/test/tokenomics/blockFee.test.ts | 4 +- .../test/tokenomics/proofReward.test.ts | 4 +- packages/protocol/test/utils/blockListener.ts | 8 +- .../{tokenomics/utils.ts => utils/fixture.ts} | 28 ++-- packages/protocol/test/utils/onNewL2Block.ts | 8 +- 7 files changed, 107 insertions(+), 81 deletions(-) rename packages/protocol/test/{tokenomics/utils.ts => utils/fixture.ts} (82%) diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index 1b48484259..86921b823a 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -19,7 +19,7 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.chainId = 167; // up to 2048 pending blocks config.maxNumBlocks = 4; - config.blockHashHistory = 1; + config.blockHashHistory = 3; // This number is calculated from maxNumBlocks to make // the 'the maximum value of the multiplier' close to 20.0 config.zkProofsPerBlock = 1; diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 7315317eb0..5f3521c778 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -1,73 +1,52 @@ import { expect } from "chai"; +import { SimpleChannel } from "channel-ts"; import { BigNumber, ethers as ethersLib } from "ethers"; import { ethers } from "hardhat"; -import { TaikoL1, TaikoL2 } from "../../typechain"; -import deployAddressManager from "../utils/addressManager"; +import { TaikoL1, TestTkoToken } from "../../typechain"; +import blockListener from "../utils/blockListener"; import { BlockMetadata } from "../utils/block_metadata"; import { commitAndProposeLatestBlock, commitBlock, generateCommitHash, } from "../utils/commit"; +import { initIntegrationFixture } from "../utils/fixture"; import { buildProposeBlockInputs } from "../utils/propose"; +import Proposer from "../utils/proposer"; import { proveBlock } from "../utils/prove"; -import { - getDefaultL2Signer, - getL1Provider, - getL2Provider, -} from "../utils/provider"; +import Prover from "../utils/prover"; import { sendTinyEtherToZeroAddress } from "../utils/seed"; -import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; -import { deployTaikoL2 } from "../utils/taikoL2"; +import { commitProposeProveAndVerify } from "../utils/verify"; describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; - let taikoL2: TaikoL2; let l2Provider: ethersLib.providers.JsonRpcProvider; - let l2Signer: any; let l1Signer: any; + let proposerSigner: any; + let genesisHeight: number; + let tkoTokenL1: TestTkoToken; + let chan: SimpleChannel; + let interval: any; + let proverSigner: any; beforeEach(async function () { - l2Provider = getL2Provider(); - - l2Signer = await getDefaultL2Signer(); - - const l2AddressManager = await deployAddressManager(l2Signer); - taikoL2 = await deployTaikoL2(l2Signer, l2AddressManager); - - const genesisHash = taikoL2.deployTransaction.blockHash as string; - - const l1Provider = getL1Provider(); - - l1Provider.pollingInterval = 100; - - const signers = await ethers.getSigners(); - l1Signer = signers[0]; - - const l1AddressManager = await deployAddressManager(l1Signer); - - taikoL1 = await deployTaikoL1( - l1AddressManager, - genesisHash, - false, - defaultFeeBase - ); - - const { chainId: l2ChainId } = await l2Provider.getNetwork(); - - await l1AddressManager.setAddress( - `${l2ChainId}.taiko`, - taikoL2.address - ); - - const { chainId } = await l1Provider.getNetwork(); + ({ + taikoL1, + l2Provider, + l1Signer, + genesisHeight, + proposerSigner, + proverSigner, + interval, + } = await initIntegrationFixture(false, false)); + + chan = new SimpleChannel(); + }); - await ( - await l1AddressManager.setAddress( - `${chainId}.proof_verifier`, - taikoL1.address - ) - ).wait(1); + afterEach(() => { + clearInterval(interval); + l2Provider.off("block"); + chan.close(); }); describe("isCommitValid()", async function () { @@ -315,4 +294,59 @@ describe("integration:TaikoL1", function () { ).to.be.revertedWith("L1:tooMany"); }); }); + + describe("getLatestSyncedHeader", function () { + it("iterates through blockHashHistory length and asserts getLatestsyncedHeader returns correct value", async function () { + const { blockHashHistory, maxNumBlocks, commitConfirmations } = + await taikoL1.getConfig(); + + const proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + commitConfirmations.toNumber(), + maxNumBlocks.toNumber(), + 0, + proposerSigner + ); + + const prover = new Prover(taikoL1, l2Provider, proverSigner); + + l2Provider.on( + "block", + blockListener( + chan, + genesisHeight, + l2Provider, + maxNumBlocks.toNumber() + ) + ); + + let blocks: number = 0; + // iterate through blockHashHistory twice and try to get latest synced header each time. + // we modulo the header height by blockHashHistory in the protocol, so + // this test ensures that logic is sound. + /* eslint-disable-next-line */ + for await (const blockNumber of chan) { + if (blocks > blockHashHistory.toNumber() * 2 + 1) { + chan.close(); + return; + } + + const { verifyEvent } = await commitProposeProveAndVerify( + taikoL1, + l2Provider, + blockNumber, + proposer, + tkoTokenL1, + prover + ); + + expect(verifyEvent).not.to.be.undefined; + + const header = await taikoL1.getLatestSyncedHeader(); + expect(header).to.be.eq(verifyEvent.args.blockHash); + blocks++; + } + }); + }); }); diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 3055eb5005..ea450f8fd9 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -9,7 +9,7 @@ import Proposer from "../utils/proposer"; import sleep from "../utils/sleep"; import { deployTaikoL1 } from "../utils/taikoL1"; -import { initTokenomicsFixture } from "./utils"; +import { initIntegrationFixture } from "./utils"; describe("tokenomics: blockFee", function () { let taikoL1: TaikoL1; @@ -32,7 +32,7 @@ describe("tokenomics: blockFee", function () { tkoTokenL1, l1AddressManager, interval, - } = await initTokenomicsFixture()); + } = await initIntegrationFixture()); chan = new SimpleChannel(); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index e3951152ac..a9a8552041 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -9,7 +9,7 @@ import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; import { createAndSeedWallets } from "../utils/seed"; import { commitProposeProveAndVerify } from "../utils/verify"; -import { initTokenomicsFixture } from "./utils"; +import { initIntegrationFixture } from "../utils/fixture"; describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; @@ -32,7 +32,7 @@ describe("tokenomics: proofReward", function () { genesisHeight, tkoTokenL1, interval, - } = await initTokenomicsFixture()); + } = await initIntegrationFixture(true, true)); chan = new SimpleChannel(); }); diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index adf4b51afc..12bab3c194 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -14,16 +14,10 @@ const blockListener = function ( chan: SimpleChannel, genesisHeight: number, l2Provider: ethers.providers.JsonRpcProvider, - maxNumBlocks: number + maxNumBlocks?: number ) { return function (blockNumber: number) { if (blockNumber < genesisHeight) return; - if (blockNumber > genesisHeight + (maxNumBlocks - 1)) { - chan.close(); - l2Provider.off("block"); - return; - } - chan.send(blockNumber); }; }; diff --git a/packages/protocol/test/tokenomics/utils.ts b/packages/protocol/test/utils/fixture.ts similarity index 82% rename from packages/protocol/test/tokenomics/utils.ts rename to packages/protocol/test/utils/fixture.ts index 19fe96cf36..00416f1499 100644 --- a/packages/protocol/test/tokenomics/utils.ts +++ b/packages/protocol/test/utils/fixture.ts @@ -1,20 +1,16 @@ import { ethers } from "ethers"; -import deployAddressManager from "../utils/addressManager"; -import { - getDefaultL2Signer, - getL1Provider, - getL2Provider, -} from "../utils/provider"; -import { defaultFeeBase, deployTaikoL1 } from "../utils/taikoL1"; -import { deployTaikoL2 } from "../utils/taikoL2"; -import deployTkoToken from "../utils/tkoToken"; +import deployAddressManager from "./addressManager"; +import { getDefaultL2Signer, getL1Provider, getL2Provider } from "./provider"; +import { defaultFeeBase, deployTaikoL1 } from "./taikoL1"; +import { deployTaikoL2 } from "./taikoL2"; +import deployTkoToken from "./tkoToken"; import { ethers as hardhatEthers } from "hardhat"; -import { - createAndSeedWallets, - sendTinyEtherToZeroAddress, -} from "../utils/seed"; +import { createAndSeedWallets, sendTinyEtherToZeroAddress } from "./seed"; -async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { +async function initIntegrationFixture( + mintTkoToProposer: boolean, + enableTokenomics: boolean = true +) { const l1Provider = getL1Provider(); l1Provider.pollingInterval = 100; @@ -36,7 +32,7 @@ async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { const taikoL1 = await deployTaikoL1( l1AddressManager, genesisHash, - true, + enableTokenomics, defaultFeeBase ); const { chainId } = await l1Provider.getNetwork(); @@ -115,4 +111,4 @@ async function initTokenomicsFixture(mintTkoToProposer: boolean = true) { }; } -export { initTokenomicsFixture }; +export { initIntegrationFixture }; diff --git a/packages/protocol/test/utils/onNewL2Block.ts b/packages/protocol/test/utils/onNewL2Block.ts index 1ebd6ffe8a..e1691da027 100644 --- a/packages/protocol/test/utils/onNewL2Block.ts +++ b/packages/protocol/test/utils/onNewL2Block.ts @@ -29,14 +29,16 @@ async function onNewL2Block( const { id, meta } = proposedEvent.args; + const { enableTokenomics } = await taikoL1.getConfig(); + const newProofReward = await taikoL1.getProofReward( new Date().getMilliseconds(), meta.timestamp ); - const newProposerTkoBalance = await tkoTokenL1.balanceOf( - await proposerSigner.getAddress() - ); + const newProposerTkoBalance = enableTokenomics + ? await tkoTokenL1.balanceOf(await proposerSigner.getAddress()) + : BigNumber.from(0); const newBlockFee = await taikoL1.getBlockFee(); From de2b21596395866d68addff9a71f5e4f9ba313ee Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 12:05:02 -0800 Subject: [PATCH 40/45] add test:all script so all tests can be run before pushing to verify nothing broke --- packages/protocol/package.json | 1 + packages/protocol/test/tokenomics/blockFee.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 9690d5b657..643841aca7 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -18,6 +18,7 @@ "test:genesis": "./test/genesis/generate_genesis.test.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", "deploy:hardhat": "./scripts/download_solc.sh && LOG_LEVEL=debug pnpm hardhat deploy_L1 --network hardhat --dao-vault 0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39 --team-vault 0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39 --l2-genesis-block-hash 0xee1950562d42f0da28bd4550d88886bc90894c77c9c9eaefef775d4c8223f259 --bridge-funder-private-key ddbf12f72c946bb1e6de5eaf580c51db51828ba198d9b0dba9c7d48ec748dc04 --bridge-fund 0xff --oracle-prover 0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39 --confirmations 1", "lint-staged": "lint-staged --allow-empty" }, diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index ea450f8fd9..11b68817a9 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -9,7 +9,7 @@ import Proposer from "../utils/proposer"; import sleep from "../utils/sleep"; import { deployTaikoL1 } from "../utils/taikoL1"; -import { initIntegrationFixture } from "./utils"; +import { initIntegrationFixture } from "../utils/fixture"; describe("tokenomics: blockFee", function () { let taikoL1: TaikoL1; @@ -32,7 +32,7 @@ describe("tokenomics: blockFee", function () { tkoTokenL1, l1AddressManager, interval, - } = await initIntegrationFixture()); + } = await initIntegrationFixture(true, true)); chan = new SimpleChannel(); }); From ba940852ca975a097f9e661bfe5cfa6f8a7e2282 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 12:49:52 -0800 Subject: [PATCH 41/45] propose/verify halt tests, refactor, halt utils method --- .../test/L1/TaikoL1.integration.test.ts | 72 +++++++++++++------ .../test/tokenomics/proofReward.test.ts | 26 ++++--- packages/protocol/test/utils/halt.ts | 11 +++ packages/protocol/test/utils/verify.ts | 9 +-- 4 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 packages/protocol/test/utils/halt.ts diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 5f3521c778..a529cf145d 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -11,12 +11,14 @@ import { generateCommitHash, } from "../utils/commit"; import { initIntegrationFixture } from "../utils/fixture"; +import halt from "../utils/halt"; +import { onNewL2Block } from "../utils/onNewL2Block"; import { buildProposeBlockInputs } from "../utils/propose"; import Proposer from "../utils/proposer"; import { proveBlock } from "../utils/prove"; import Prover from "../utils/prover"; import { sendTinyEtherToZeroAddress } from "../utils/seed"; -import { commitProposeProveAndVerify } from "../utils/verify"; +import { commitProposeProveAndVerify, verifyBlocks } from "../utils/verify"; describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; @@ -28,6 +30,10 @@ describe("integration:TaikoL1", function () { let chan: SimpleChannel; let interval: any; let proverSigner: any; + let proposer: Proposer; + let prover: Prover; + /* eslint-disable-next-line */ + let config: Awaited>; beforeEach(async function () { ({ @@ -40,6 +46,19 @@ describe("integration:TaikoL1", function () { interval, } = await initIntegrationFixture(false, false)); + config = await taikoL1.getConfig(); + + proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), + 0, + proposerSigner + ); + + prover = new Prover(taikoL1, l2Provider, proverSigner); + chan = new SimpleChannel(); }); @@ -64,7 +83,6 @@ describe("integration:TaikoL1", function () { }); it("should be valid if it has been committed", async function () { - const { commitConfirmations } = await taikoL1.getConfig(); const block = await l2Provider.getBlock("latest"); const commitSlot = 0; const { commit, blockCommittedEvent } = await commitBlock( @@ -74,7 +92,7 @@ describe("integration:TaikoL1", function () { ); expect(blockCommittedEvent).not.to.be.undefined; - for (let i = 0; i < commitConfirmations.toNumber(); i++) { + for (let i = 0; i < config.commitConfirmations.toNumber(); i++) { await sendTinyEtherToZeroAddress(l1Signer); } @@ -259,10 +277,9 @@ describe("integration:TaikoL1", function () { }); it("should commit and be able to propose for all available slots, then revert when all slots are taken", async function () { - const { maxNumBlocks } = await taikoL1.getConfig(); // propose blocks and fill up maxNumBlocks number of slots, // expect each one to be successful. - for (let i = 0; i < maxNumBlocks.toNumber() - 1; i++) { + for (let i = 0; i < config.maxNumBlocks.toNumber() - 1; i++) { await commitAndProposeLatestBlock( taikoL1, l1Signer, @@ -297,27 +314,13 @@ describe("integration:TaikoL1", function () { describe("getLatestSyncedHeader", function () { it("iterates through blockHashHistory length and asserts getLatestsyncedHeader returns correct value", async function () { - const { blockHashHistory, maxNumBlocks, commitConfirmations } = - await taikoL1.getConfig(); - - const proposer = new Proposer( - taikoL1.connect(proposerSigner), - l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), - 0, - proposerSigner - ); - - const prover = new Prover(taikoL1, l2Provider, proverSigner); - l2Provider.on( "block", blockListener( chan, genesisHeight, l2Provider, - maxNumBlocks.toNumber() + config.maxNumBlocks.toNumber() ) ); @@ -327,7 +330,7 @@ describe("integration:TaikoL1", function () { // this test ensures that logic is sound. /* eslint-disable-next-line */ for await (const blockNumber of chan) { - if (blocks > blockHashHistory.toNumber() * 2 + 1) { + if (blocks > config.blockHashHistory.toNumber() * 2 + 1) { chan.close(); return; } @@ -349,4 +352,31 @@ describe("integration:TaikoL1", function () { } }); }); + + describe("proposeBlock", function () { + it("can not propose if chain is halted", async function () { + await halt(taikoL1.connect(l1Signer), true); + + await expect( + onNewL2Block( + l2Provider, + await l2Provider.getBlockNumber(), + proposer, + taikoL1, + proposerSigner, + tkoTokenL1 + ) + ).to.be.reverted; + }); + }); + + describe("verifyBlocks", function () { + it("can not be called manually to verify block if chain is halted", async function () { + await halt(taikoL1.connect(l1Signer), true); + + await expect(verifyBlocks(taikoL1, 1)).to.be.revertedWith( + "L1:halted" + ); + }); + }); }); diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index a9a8552041..2f1a84fe98 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -22,6 +22,9 @@ describe("tokenomics: proofReward", function () { let interval: any; let chan: SimpleChannel; + /* eslint-disable-next-line */ + let config: Awaited>; + beforeEach(async () => { ({ taikoL1, @@ -34,6 +37,7 @@ describe("tokenomics: proofReward", function () { interval, } = await initIntegrationFixture(true, true)); chan = new SimpleChannel(); + config = await taikoL1.getConfig(); }); afterEach(() => { @@ -43,13 +47,11 @@ describe("tokenomics: proofReward", function () { }); it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { - const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const proposer = new Proposer( taikoL1.connect(proposerSigner), l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), 0, proposerSigner ); @@ -98,13 +100,11 @@ describe("tokenomics: proofReward", function () { the provers TKO balance should increase as the blocks are verified and they receive the proofReward. the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { - const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const proposer = new Proposer( taikoL1.connect(proposerSigner), l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), 0, proposerSigner ); @@ -127,7 +127,7 @@ describe("tokenomics: proofReward", function () { chan, genesisHeight, l2Provider, - maxNumBlocks.toNumber() + config.maxNumBlocks.toNumber() ) ); @@ -162,15 +162,13 @@ describe("tokenomics: proofReward", function () { the provers TKO balance should increase as the blocks are verified and they receive the proofReward. the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { - const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - const proposers = (await createAndSeedWallets(3, l1Signer)).map( (p: ethers.Wallet) => new Proposer( taikoL1.connect(p), l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), 0, p ) @@ -217,7 +215,7 @@ describe("tokenomics: proofReward", function () { chan, genesisHeight, l2Provider, - maxNumBlocks.toNumber() + config.maxNumBlocks.toNumber() ) ); diff --git a/packages/protocol/test/utils/halt.ts b/packages/protocol/test/utils/halt.ts new file mode 100644 index 0000000000..1f0efb86c4 --- /dev/null +++ b/packages/protocol/test/utils/halt.ts @@ -0,0 +1,11 @@ +import { expect } from "chai"; +import { TaikoL1 } from "../../typechain"; + +async function halt(taikoL1: TaikoL1, halt: boolean) { + await (await taikoL1.halt(true)).wait(1); + const isHalted = await taikoL1.isHalted(); + expect(isHalted).to.be.eq(halt); + return isHalted; +} + +export default halt; diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts index ed005466cb..873ab8cfc1 100644 --- a/packages/protocol/test/utils/verify.ts +++ b/packages/protocol/test/utils/verify.ts @@ -32,8 +32,7 @@ async function verifyBlockAndAssert( taikoL1: TaikoL1, tkoTokenL1: TkoToken, block: BlockInfo, - lastProofReward: BigNumber, - compareHeaders: boolean = false + lastProofReward: BigNumber ): Promise<{ newProofReward: BigNumber }> { await sleepUntilBlockIsVerifiable(taikoL1, block.id, block.provenAt); @@ -44,12 +43,6 @@ async function verifyBlockAndAssert( expect(isVerifiable).to.be.eq(true); - // dont verify first blocks parent hash, because we arent "real L2" in these - // tests, the parent hash will be wrong. - // if (compareHeaders) { - // const latestHash = await taikoL1.getLatestSyncedHeader(); - // expect(latestHash).to.be.eq(block.parentHash); - // } const prover = block.forkChoice.provers[0]; const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf( From cdc70d8f930bd3c2c485c675f0081bb3b105bd29 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 12:53:44 -0800 Subject: [PATCH 42/45] chan/config to integrations fixture --- .../test/L1/TaikoL1.integration.test.ts | 7 ++----- .../protocol/test/tokenomics/blockFee.test.ts | 17 ++++++++--------- .../test/tokenomics/proofReward.test.ts | 4 ++-- packages/protocol/test/utils/fixture.ts | 5 +++++ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index a529cf145d..e3ce6ee734 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -44,10 +44,9 @@ describe("integration:TaikoL1", function () { proposerSigner, proverSigner, interval, + chan, + config, } = await initIntegrationFixture(false, false)); - - config = await taikoL1.getConfig(); - proposer = new Proposer( taikoL1.connect(proposerSigner), l2Provider, @@ -58,8 +57,6 @@ describe("integration:TaikoL1", function () { ); prover = new Prover(taikoL1, l2Provider, proverSigner); - - chan = new SimpleChannel(); }); afterEach(() => { diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 11b68817a9..85dc6a43ed 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -21,6 +21,8 @@ describe("tokenomics: blockFee", function () { let l1AddressManager: AddressManager; let interval: any; let chan: SimpleChannel; + /* eslint-disable-next-line */ + let config: Awaited>; beforeEach(async () => { ({ @@ -32,8 +34,9 @@ describe("tokenomics: blockFee", function () { tkoTokenL1, l1AddressManager, interval, + chan, + config, } = await initIntegrationFixture(true, true)); - chan = new SimpleChannel(); }); afterEach(() => clearInterval(interval)); @@ -46,10 +49,8 @@ describe("tokenomics: blockFee", function () { }); it("block fee should increase as the halving period passes, while no blocks are proposed", async function () { - const { bootstrapDiscountHalvingPeriod } = await taikoL1.getConfig(); - const iterations: number = 5; - const period: number = bootstrapDiscountHalvingPeriod + const period: number = config.bootstrapDiscountHalvingPeriod .mul(1000) .toNumber(); @@ -64,14 +65,12 @@ describe("tokenomics: blockFee", function () { }); it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since more slots are used and no proofs have been submitted", async function () { - const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); - // set up a proposer to continually propose new blocks const proposer = new Proposer( taikoL1.connect(proposerSigner), l2Provider, - commitConfirmations.toNumber(), - maxNumBlocks.toNumber(), + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), 0, proposerSigner ); @@ -99,7 +98,7 @@ describe("tokenomics: blockFee", function () { chan, genesisHeight, l2Provider, - maxNumBlocks.toNumber() + config.maxNumBlocks.toNumber() ) ); /* eslint-disable-next-line */ diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 2f1a84fe98..1124480067 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -35,9 +35,9 @@ describe("tokenomics: proofReward", function () { genesisHeight, tkoTokenL1, interval, + chan, + config, } = await initIntegrationFixture(true, true)); - chan = new SimpleChannel(); - config = await taikoL1.getConfig(); }); afterEach(() => { diff --git a/packages/protocol/test/utils/fixture.ts b/packages/protocol/test/utils/fixture.ts index 00416f1499..d14ad424b4 100644 --- a/packages/protocol/test/utils/fixture.ts +++ b/packages/protocol/test/utils/fixture.ts @@ -6,6 +6,7 @@ import { deployTaikoL2 } from "./taikoL2"; import deployTkoToken from "./tkoToken"; import { ethers as hardhatEthers } from "hardhat"; import { createAndSeedWallets, sendTinyEtherToZeroAddress } from "./seed"; +import { SimpleChannel } from "channel-ts"; async function initIntegrationFixture( mintTkoToProposer: boolean, @@ -94,6 +95,8 @@ async function initIntegrationFixture( }); await tx.wait(1); + const chan = new SimpleChannel(); + const config = await taikoL1.getConfig(); return { taikoL1, taikoL2, @@ -108,6 +111,8 @@ async function initIntegrationFixture( tkoTokenL1, l1AddressManager, interval, + chan, + config, }; } From 6de3a2c93dab4ea5502e8eaff0e4ccedbefe4a38 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 12:57:46 -0800 Subject: [PATCH 43/45] add proposer/prover to fixture for re-use --- .../protocol/test/tokenomics/blockFee.test.ts | 12 ++------- .../test/tokenomics/proofReward.test.ts | 27 +++---------------- packages/protocol/test/utils/fixture.ts | 15 +++++++++++ 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 85dc6a43ed..cb1d50205c 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -23,6 +23,7 @@ describe("tokenomics: blockFee", function () { let chan: SimpleChannel; /* eslint-disable-next-line */ let config: Awaited>; + let proposer: Proposer; beforeEach(async () => { ({ @@ -36,6 +37,7 @@ describe("tokenomics: blockFee", function () { interval, chan, config, + proposer, } = await initIntegrationFixture(true, true)); }); @@ -65,16 +67,6 @@ describe("tokenomics: blockFee", function () { }); it("proposes blocks on interval, blockFee should increase, proposer's balance for TKOToken should decrease as it pays proposer fee, proofReward should increase since more slots are used and no proofs have been submitted", async function () { - // set up a proposer to continually propose new blocks - const proposer = new Proposer( - taikoL1.connect(proposerSigner), - l2Provider, - config.commitConfirmations.toNumber(), - config.maxNumBlocks.toNumber(), - 0, - proposerSigner - ); - // get the initial tkoBalance, which should decrease every block proposal let lastProposerTkoBalance = await tkoTokenL1.balanceOf( await proposerSigner.getAddress() diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 1124480067..6a50e820b1 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -15,12 +15,13 @@ describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; let l2Provider: ethers.providers.JsonRpcProvider; let l1Signer: any; - let proposerSigner: any; let proverSigner: any; let genesisHeight: number; let tkoTokenL1: TestTkoToken; let interval: any; let chan: SimpleChannel; + let proposer: Proposer; + let prover: Prover; /* eslint-disable-next-line */ let config: Awaited>; @@ -30,13 +31,14 @@ describe("tokenomics: proofReward", function () { taikoL1, l2Provider, l1Signer, - proposerSigner, proverSigner, genesisHeight, tkoTokenL1, interval, chan, config, + proposer, + prover, } = await initIntegrationFixture(true, true)); }); @@ -47,16 +49,6 @@ describe("tokenomics: proofReward", function () { }); it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { - const proposer = new Proposer( - taikoL1.connect(proposerSigner), - l2Provider, - config.commitConfirmations.toNumber(), - config.maxNumBlocks.toNumber(), - 0, - proposerSigner - ); - - const prover = new Prover(taikoL1, l2Provider, proverSigner); let proposed: boolean = false; l2Provider.on("block", function (blockNumber: number) { if (proposed) { @@ -100,17 +92,6 @@ describe("tokenomics: proofReward", function () { the provers TKO balance should increase as the blocks are verified and they receive the proofReward. the proposer should receive a refund on his deposit because he holds a tkoBalance > 0 at time of verification.`, async function () { - const proposer = new Proposer( - taikoL1.connect(proposerSigner), - l2Provider, - config.commitConfirmations.toNumber(), - config.maxNumBlocks.toNumber(), - 0, - proposerSigner - ); - - const prover = new Prover(taikoL1, l2Provider, proverSigner); - // prover needs TKO or their reward will be cut down to 1 wei. await ( await tkoTokenL1 diff --git a/packages/protocol/test/utils/fixture.ts b/packages/protocol/test/utils/fixture.ts index d14ad424b4..dd47b00130 100644 --- a/packages/protocol/test/utils/fixture.ts +++ b/packages/protocol/test/utils/fixture.ts @@ -7,6 +7,8 @@ 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, @@ -97,6 +99,17 @@ async function initIntegrationFixture( const chan = new SimpleChannel(); const config = await taikoL1.getConfig(); + + const proposer = new Proposer( + taikoL1.connect(proposerSigner), + l2Provider, + config.commitConfirmations.toNumber(), + config.maxNumBlocks.toNumber(), + 0, + proposerSigner + ); + + const prover = new Prover(taikoL1, l2Provider, proverSigner); return { taikoL1, taikoL2, @@ -113,6 +126,8 @@ async function initIntegrationFixture( interval, chan, config, + proposer, + prover, }; } From a064c56fe438712a3c91f727bed02d9cc934fa79 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 16:07:41 -0800 Subject: [PATCH 44/45] test fix --- .../test/L1/TaikoL1.integration.test.ts | 10 +------ .../protocol/test/tokenomics/blockFee.test.ts | 10 +------ .../test/tokenomics/proofReward.test.ts | 26 ++++++------------- packages/protocol/test/utils/blockListener.ts | 7 ++--- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index e3ce6ee734..b6a11db754 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -311,15 +311,7 @@ describe("integration:TaikoL1", function () { describe("getLatestSyncedHeader", function () { it("iterates through blockHashHistory length and asserts getLatestsyncedHeader returns correct value", async function () { - l2Provider.on( - "block", - blockListener( - chan, - genesisHeight, - l2Provider, - config.maxNumBlocks.toNumber() - ) - ); + l2Provider.on("block", blockListener(chan, genesisHeight)); let blocks: number = 0; // iterate through blockHashHistory twice and try to get latest synced header each time. diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index cb1d50205c..5a64ba5d9c 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -84,15 +84,7 @@ describe("tokenomics: blockFee", function () { let lastProofReward = BigNumber.from(0); - l2Provider.on( - "block", - blockListener( - chan, - genesisHeight, - l2Provider, - config.maxNumBlocks.toNumber() - ) - ); + l2Provider.on("block", blockListener(chan, genesisHeight)); /* eslint-disable-next-line */ for await (const blockNumber of chan) { const { newProposerTkoBalance, newBlockFee, newProofReward } = diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 6a50e820b1..c02709792b 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -102,18 +102,13 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); - l2Provider.on( - "block", - blockListener( - chan, - genesisHeight, - l2Provider, - config.maxNumBlocks.toNumber() - ) - ); + l2Provider.on("block", blockListener(chan, genesisHeight)); /* eslint-disable-next-line */ for await (const blockNumber of chan) { + if (blockNumber > genesisHeight + config.maxNumBlocks.toNumber()) { + break; + } const proverTkoBalanceBeforeVerification = await tkoTokenL1.balanceOf(await prover.getSigner().address); @@ -190,18 +185,13 @@ describe("tokenomics: proofReward", function () { ) ).wait(1); - l2Provider.on( - "block", - blockListener( - chan, - genesisHeight, - l2Provider, - config.maxNumBlocks.toNumber() - ) - ); + l2Provider.on("block", blockListener(chan, genesisHeight)); /* eslint-disable-next-line */ for await (const blockNumber of chan) { + if (blockNumber > genesisHeight + config.maxNumBlocks.toNumber()) { + break; + } const prover = pickRandomElement(provers); const proposer = pickRandomElement(proposers); const proverTkoBalanceBefore = await tkoTokenL1.balanceOf( diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index 12bab3c194..363663ad39 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -1,8 +1,7 @@ import type { SimpleChannel } from "channel-ts"; -import { ethers } from "ethers"; // blockListener should be called as follows: -// `l2Provider.on("block", blockListener(chan, genesisHeight, l2Provider, maxNumBlocks))` +// `l2Provider.on("block", blockListener(chan, genesisHeight)` // it will send incoming blockNumbers, generated from the l2 provider on a new block, // through a Golang-style channel, which can then be waited on like such: // for await (const blockNumber of chan) @@ -12,9 +11,7 @@ import { ethers } from "ethers"; const blockListener = function ( chan: SimpleChannel, - genesisHeight: number, - l2Provider: ethers.providers.JsonRpcProvider, - maxNumBlocks?: number + genesisHeight: number ) { return function (blockNumber: number) { if (blockNumber < genesisHeight) return; From b965a571426399317d89167b1da8537cc8fea613 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 2 Feb 2023 16:24:39 -0800 Subject: [PATCH 45/45] test check maxnum --- packages/protocol/test/tokenomics/blockFee.test.ts | 6 ++++++ packages/protocol/test/tokenomics/proofReward.test.ts | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 5a64ba5d9c..1ea98bec61 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -87,6 +87,12 @@ describe("tokenomics: blockFee", function () { l2Provider.on("block", blockListener(chan, genesisHeight)); /* eslint-disable-next-line */ for await (const blockNumber of chan) { + if ( + blockNumber > + genesisHeight + (config.maxNumBlocks.toNumber() - 1) + ) { + break; + } const { newProposerTkoBalance, newBlockFee, newProofReward } = await onNewL2Block( l2Provider, diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index c02709792b..0b349e659a 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -106,7 +106,10 @@ describe("tokenomics: proofReward", function () { /* eslint-disable-next-line */ for await (const blockNumber of chan) { - if (blockNumber > genesisHeight + config.maxNumBlocks.toNumber()) { + if ( + blockNumber > + genesisHeight + (config.maxNumBlocks.toNumber() - 1) + ) { break; } const proverTkoBalanceBeforeVerification =