diff --git a/packages/protocol/contracts/libs/LibBlockHeader.sol b/packages/protocol/contracts/libs/LibBlockHeader.sol index cc5269dc59c..4525b8cb5fa 100644 --- a/packages/protocol/contracts/libs/LibBlockHeader.sol +++ b/packages/protocol/contracts/libs/LibBlockHeader.sol @@ -25,6 +25,7 @@ struct BlockHeader { bytes32 mixHash; uint64 nonce; uint256 baseFeePerGas; + bytes32 withdrawalsRoot; } library LibBlockHeader { @@ -44,12 +45,15 @@ library LibBlockHeader { BlockHeader memory header, uint256 extraCapacity ) internal pure returns (bytes[] memory list) { - if (header.baseFeePerGas == 0) { - // non-EIP11559 transaction - list = new bytes[](15 + extraCapacity); - } else { - // EIP1159 transaction + if (header.withdrawalsRoot != 0) { + // EIP-4895 transaction + list = new bytes[](17 + extraCapacity); + } else if (header.baseFeePerGas != 0) { + // EIP-1559 transaction list = new bytes[](16 + extraCapacity); + } else { + // non-EIP-1559 transaction + list = new bytes[](15 + extraCapacity); } list[0] = LibRLPWriter.writeHash(header.parentHash); list[1] = LibRLPWriter.writeHash(header.ommersHash); @@ -69,9 +73,13 @@ library LibBlockHeader { // as [8]byte when hashing the block. list[14] = LibRLPWriter.writeBytes(abi.encodePacked(header.nonce)); if (header.baseFeePerGas != 0) { - // non-EIP11559 transaction + // EIP-1559 transaction list[15] = LibRLPWriter.writeUint(header.baseFeePerGas); } + if (header.withdrawalsRoot != 0) { + // EIP-4895 transaction + list[16] = LibRLPWriter.writeHash(header.withdrawalsRoot); + } } function isPartiallyValidForTaiko( diff --git a/packages/protocol/test/libs/LibBlockHeader.test.ts b/packages/protocol/test/libs/LibBlockHeader.test.ts index 7959d0752d3..ba37b402a56 100644 --- a/packages/protocol/test/libs/LibBlockHeader.test.ts +++ b/packages/protocol/test/libs/LibBlockHeader.test.ts @@ -46,6 +46,7 @@ describe("LibBlockHeader tests", function () { "0xf5ba25df1e92e89a09e0b32063b81795f631100801158f5fa733f2ba26843bd0", nonce: EBN.from("0x738b7e38476abe98"), baseFeePerGas: 0, + withdrawalsRoot: ethers.constants.HashZero, }; const headerComputed = await libBlockHeader.hashBlockHeader( @@ -87,6 +88,7 @@ describe("LibBlockHeader tests", function () { mixHash: ethers.constants.HashZero, nonce: EBN.from("0x0"), baseFeePerGas: 0, + withdrawalsRoot: ethers.constants.HashZero, }; const headerComputed = await libBlockHeader.hashBlockHeader( @@ -96,7 +98,7 @@ describe("LibBlockHeader tests", function () { expect(headerComputed).to.equal(blockHash); }); - it("can calculate EIP1159", async function () { + it("can calculate EIP-1559", async function () { const blockHash = "0xb39b05b327d23ca29286fa7e2331d8269cd257cf10a8310b40ebaddf411f191e"; // block 0xb39b05b327d23ca29286fa7e2331d8269cd257cf10a8310b40ebaddf411f191e on our L1 testnet @@ -130,6 +132,52 @@ describe("LibBlockHeader tests", function () { "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0", baseFeePerGas: EBN.from("0x37"), + withdrawalsRoot: ethers.constants.HashZero, + }; + + const headerComputed = await libBlockHeader.hashBlockHeader( + l2BlockHeader + ); + log.debug("headerComputed:", headerComputed); + + expect(headerComputed).to.equal(blockHash); + }); + + it("can hash post Shanghai fork blocks", async function () { + const blockHash = + "0x0fb703aea6875ca7e78a73552064cf96a0985879c3b1fa27c846d41b1aa4e98e"; + // block 0x0fb703aea6875ca7e78a73552064cf96a0985879c3b1fa27c846d41b1aa4e98e on Sepolia. + + const parentHash = + "0xe122a2cc28199703e3ed6bee61875dc1703aa97c3187d8df507f1f789e363977"; + + const l2BlockHeader: any = { + parentHash: parentHash, + ommersHash: + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + beneficiary: "0x3826539cbd8d68dcf119e80b994557b4278cec9f", + stateRoot: + "0x2525478f8cd349640e6d1780334e63c56fa0bfb44d89fbd75cb3fcf82d38521c", + transactionsRoot: + "0x97ea65e77b43073c372976694916209b916caa0caef1445d53e9757b9824bdef", + receiptsRoot: + "0xa8b034a217a5d4d4615ee0ed28c4d5275b93888c01c65a98fcdd1aeda6903e1c", + logsBloom: + "00000004000000000000000000100000000100000010001000000000000000000000880000000000000400001100000000000000000000000000000010300000000000000000000000000008000000000000200000000000000000008080000000002000020040000200000400000900000000000000000010000014000000000020024008000000000800000000900000211001000040002000000000000810021000000040400000102000000000040400002000000000000008000000001000008002300000000000000020000008004000000001000110000001000028000014440800010000000000000000000004210000000000400000000000000000" + .match(/.{1,64}/g)! + .map((s) => "0x" + s), + difficulty: EBN.from("0x0"), + height: EBN.from("0x2e93c3"), + gasLimit: EBN.from("0x1c9c380"), + gasUsed: EBN.from("0x44dd61"), + timestamp: EBN.from("0x64097a78"), + extraData: "0x", + mixHash: + "0xa3fedc5083947ffb01157d6a89aa00f0592e14b94da8768ac0e6ded0aa490eb8", + nonce: "0x0000000000000000", + baseFeePerGas: EBN.from("0x7"), + withdrawalsRoot: + "0x0975bba9482fab7591735f9dc8c344078d27abac007086d5dd62ee3a21e3ed29", }; const headerComputed = await libBlockHeader.hashBlockHeader( diff --git a/packages/protocol/test/thirdparty/LibBlockHeaderDecoder.test.ts b/packages/protocol/test/thirdparty/LibBlockHeaderDecoder.test.ts index 3c2edc94315..865dd00a96d 100644 --- a/packages/protocol/test/thirdparty/LibBlockHeaderDecoder.test.ts +++ b/packages/protocol/test/thirdparty/LibBlockHeaderDecoder.test.ts @@ -59,6 +59,7 @@ describe("LibBlockHeaderDecoder", async function () { "0xf5ba25df1e92e89a09e0b32063b81795f631100801158f5fa733f2ba26843bd0", nonce: EBN.from("0x738b7e38476abe98"), baseFeePerGas: 0, + withdrawalsRoot: ethers.constants.HashZero, }; const encodedBlockHeader = await hashBlockHeader.rlpBlockHeader( @@ -103,6 +104,7 @@ describe("LibBlockHeaderDecoder", async function () { mixHash: block.mixHash, nonce: block.nonce, baseFeePerGas: 0, + withdrawalsRoot: ethers.constants.HashZero, }; const encodedBlockHeader = await hashBlockHeader.rlpBlockHeader( blockHeader @@ -146,6 +148,7 @@ describe("LibBlockHeaderDecoder", async function () { mixHash: block.mixHash, nonce: block.nonce, baseFeePerGas: 0, + withdrawalsRoot: ethers.constants.HashZero, }; const encodedBlockHeader = await hashBlockHeader.rlpBlockHeader( blockHeader diff --git a/packages/protocol/test/utils/rpc.ts b/packages/protocol/test/utils/rpc.ts index a72ee514925..cdac87a5e6c 100644 --- a/packages/protocol/test/utils/rpc.ts +++ b/packages/protocol/test/utils/rpc.ts @@ -37,6 +37,7 @@ type Block = { uncles: string[]; baseFeePerGas?: string; mixHash: string; + withdrawalsRoot: string; }; type BlockHeader = { @@ -56,6 +57,7 @@ type BlockHeader = { mixHash: string; nonce: number; baseFeePerGas: number; + withdrawalsRoot: string; }; async function getBlockHeader( @@ -90,6 +92,8 @@ async function getBlockHeader( mixHash: block.mixHash, nonce: block.nonce, baseFeePerGas: block.baseFeePerGas ? parseInt(block.baseFeePerGas) : 0, + // set to zero for pre-shanghai L1 blocks used in the integration test node + withdrawalsRoot: ethers.constants.HashZero, }; return { block, blockHeader }; diff --git a/packages/protocol/test/utils/signal.ts b/packages/protocol/test/utils/signal.ts index b535ee1b2c8..e6ce33179ad 100644 --- a/packages/protocol/test/utils/signal.ts +++ b/packages/protocol/test/utils/signal.ts @@ -61,7 +61,7 @@ async function getSignalProof( // encode the SignalProof struct from LibBridgeSignal const signalProof = ethers.utils.defaultAbiCoder.encode( [ - "tuple(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, bytes proof)", + "tuple(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, bytes32 withdrawalsRoot) header, bytes proof)", ], [{ header: blockHeader, proof: encodedProof }] );