From 141a7b5bd9658a0176e898103f82684d122828e1 Mon Sep 17 00:00:00 2001 From: Julink Date: Mon, 15 Jul 2024 17:03:42 +0200 Subject: [PATCH 1/4] feat: make getStorageValues more flexible to accept the most recent L2 finalized blocks --- .../contracts/RollupMock.sol | 14 ++++++++ .../linea-ccip-gateway/src/L2ProofService.ts | 35 +++++++++++++++++-- .../contracts/RollupMock.sol | 21 +++++++++++ .../test/testL1Resolver.spec.ts | 4 +-- .../contracts/LineaSparseProofVerifier.sol | 11 ++++-- 5 files changed, 77 insertions(+), 8 deletions(-) diff --git a/packages/linea-ccip-gateway/contracts/RollupMock.sol b/packages/linea-ccip-gateway/contracts/RollupMock.sol index 76b11f607..2fdce2142 100644 --- a/packages/linea-ccip-gateway/contracts/RollupMock.sol +++ b/packages/linea-ccip-gateway/contracts/RollupMock.sol @@ -2,6 +2,13 @@ pragma solidity ^0.8.25; contract RollupMock { + event DataFinalized( + uint256 indexed lastBlockFinalized, + bytes32 indexed startingRootHash, + bytes32 indexed finalRootHash, + bool withProof + ); + uint256 public currentL2BlockNumber; mapping(uint256 blockNumber => bytes32 stateRootHash) public stateRootHashes; @@ -9,5 +16,12 @@ contract RollupMock { constructor(uint256 _currentL2BlockNumber, bytes32 _currentStateRootHash) { currentL2BlockNumber = _currentL2BlockNumber; stateRootHashes[currentL2BlockNumber] = _currentStateRootHash; + + emit DataFinalized( + _currentL2BlockNumber, + _currentStateRootHash, + _currentStateRootHash, + false + ); } } diff --git a/packages/linea-ccip-gateway/src/L2ProofService.ts b/packages/linea-ccip-gateway/src/L2ProofService.ts index 1776794a4..cedae047b 100644 --- a/packages/linea-ccip-gateway/src/L2ProofService.ts +++ b/packages/linea-ccip-gateway/src/L2ProofService.ts @@ -21,6 +21,7 @@ const currentL2BlockNumberSig = export class L2ProofService implements IProofService { private readonly rollup: Contract; private readonly helper: EVMProofHelper; + private readonly l1Provider: JsonRpcProvider; constructor( providerL1: JsonRpcProvider, @@ -36,6 +37,7 @@ export class L2ProofService implements IProofService { currentL2BlockNumberIface, providerL1 ); + this.l1Provider = providerL1; } /** @@ -43,9 +45,36 @@ export class L2ProofService implements IProofService { */ async getProvableBlock(): Promise { try { - const lastBlockFinalized = await this.rollup.currentL2BlockNumber(); - if (!lastBlockFinalized) throw new Error("No block found"); - return lastBlockFinalized; + const rollupAddress = await this.rollup.getAddress(); + const filterLog: ethers.Filter = { + fromBlock: 0, + toBlock: "latest", + topics: [ + "0x1335f1a2b3ff25f07f5fef07dd35d8fb4312c3c73b138e2fad9347b3319ab53c", // DataFinalized topic + ], + address: rollupAddress, + }; + const logs = await this.l1Provider.getLogs(filterLog); + const mostRecentlogs = logs.reverse(); + + if (mostRecentlogs.length === 0) { + throw new Error("No finalized block found"); + } + + // We take the latest finalized block if we only get one finalization otherwise we take the second most recent + // to avoid delays between the finalization tx and the coordinator notification update + let logIndex = 0; + if (mostRecentlogs.length > 1) { + logIndex = 1; + } + + const provableBlock = AbiCoder.defaultAbiCoder().decode( + ["uint256"], + mostRecentlogs[logIndex].topics[1] + ); + provableBlock.toString(); + + return parseInt(provableBlock.toString()); } catch (e) { logError(e); throw e; diff --git a/packages/linea-ens-resolver/contracts/RollupMock.sol b/packages/linea-ens-resolver/contracts/RollupMock.sol index 24d85e92a..9fc528781 100644 --- a/packages/linea-ens-resolver/contracts/RollupMock.sol +++ b/packages/linea-ens-resolver/contracts/RollupMock.sol @@ -2,6 +2,13 @@ pragma solidity ^0.8.25; contract RollupMock { + event DataFinalized( + uint256 indexed lastBlockFinalized, + bytes32 indexed startingRootHash, + bytes32 indexed finalRootHash, + bool withProof + ); + uint256 public currentL2BlockNumber; mapping(uint256 blockNumber => bytes32 stateRootHash) public stateRootHashes; @@ -9,6 +16,13 @@ contract RollupMock { constructor(uint256 _currentL2BlockNumber, bytes32 _currentStateRootHash) { currentL2BlockNumber = _currentL2BlockNumber; stateRootHashes[currentL2BlockNumber] = _currentStateRootHash; + + emit DataFinalized( + _currentL2BlockNumber, + _currentStateRootHash, + _currentStateRootHash, + false + ); } function setCurrentStateRoot( @@ -17,5 +31,12 @@ contract RollupMock { ) external { currentL2BlockNumber = _currentL2BlockNumber; stateRootHashes[currentL2BlockNumber] = _currentStateRootHash; + + emit DataFinalized( + _currentL2BlockNumber, + _currentStateRootHash, + _currentStateRootHash, + false + ); } } diff --git a/packages/linea-ens-resolver/test/testL1Resolver.spec.ts b/packages/linea-ens-resolver/test/testL1Resolver.spec.ts index 3b26d65a7..5155b9044 100644 --- a/packages/linea-ens-resolver/test/testL1Resolver.spec.ts +++ b/packages/linea-ens-resolver/test/testL1Resolver.spec.ts @@ -452,7 +452,7 @@ describe("Crosschain Resolver", () => { const currentBlockNo = await rollup.currentL2BlockNumber(); const currentStateRoot = await rollup.stateRootHashes(currentBlockNo); // Put a wrong block number - await rollup.setCurrentStateRoot(5, stateRoot); + await rollup.setCurrentStateRoot(blockNo + 100_000, stateRoot); let proofsEncoded = AbiCoder.defaultAbiCoder().encode( [ "uint256", @@ -466,7 +466,7 @@ describe("Crosschain Resolver", () => { await target.getStorageSlotsCallback(proofsEncoded, extraDataTest); } catch (error) { expect(error.reason).to.equal( - "LineaSparseProofVerifier: not latest finalized block" + "LineaSparseProofVerifier: block not in range accepted" ); } // Put back the right block number and state root diff --git a/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol b/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol index f2b0a3bc3..d95e46961 100644 --- a/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol +++ b/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol @@ -13,6 +13,8 @@ interface IRollup { } contract LineaSparseProofVerifier is IEVMVerifier { + uint256 public constant L2_BLOCK_RANGE_ACCEPTED = 96400; + string[] public _gatewayURLs; address public _rollup; @@ -36,10 +38,13 @@ contract LineaSparseProofVerifier is IEVMVerifier { (uint256, AccountProofStruct, StorageProofStruct[]) ); - // Check that the L2 block number used is the most recent one + // Check that the L2 block number used is a recent one + uint256 currentL2BlockNumber = IRollup(_rollup).currentL2BlockNumber(); require( - blockNo == IRollup(_rollup).currentL2BlockNumber(), - "LineaSparseProofVerifier: not latest finalized block" + (currentL2BlockNumber <= L2_BLOCK_RANGE_ACCEPTED && + blockNo <= currentL2BlockNumber) || + blockNo >= currentL2BlockNumber - L2_BLOCK_RANGE_ACCEPTED, + "LineaSparseProofVerifier: block not in range accepted" ); bytes32 stateRoot = IRollup(_rollup).stateRootHashes(blockNo); From d52253c9f98f1bad3cedc6920cd1c9eac2089034 Mon Sep 17 00:00:00 2001 From: Julink Date: Mon, 15 Jul 2024 17:38:32 +0200 Subject: [PATCH 2/4] feat: only get the logs from the last 48h --- packages/linea-ccip-gateway/src/L2ProofService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/linea-ccip-gateway/src/L2ProofService.ts b/packages/linea-ccip-gateway/src/L2ProofService.ts index cedae047b..e0931a030 100644 --- a/packages/linea-ccip-gateway/src/L2ProofService.ts +++ b/packages/linea-ccip-gateway/src/L2ProofService.ts @@ -46,8 +46,13 @@ export class L2ProofService implements IProofService { async getProvableBlock(): Promise { try { const rollupAddress = await this.rollup.getAddress(); + // Get the logs on the last 48 hours on L1 + const nbBlock48h = 14400; + const l1BlockNumber = await this.l1Provider.getBlockNumber(); + const fromBlock = + l1BlockNumber > nbBlock48h ? l1BlockNumber - nbBlock48h : 0; const filterLog: ethers.Filter = { - fromBlock: 0, + fromBlock, toBlock: "latest", topics: [ "0x1335f1a2b3ff25f07f5fef07dd35d8fb4312c3c73b138e2fad9347b3319ab53c", // DataFinalized topic From 7b0edfa8359c0e2fa055668a9bda884f8e17456d Mon Sep 17 00:00:00 2001 From: Julink Date: Mon, 15 Jul 2024 19:00:40 +0200 Subject: [PATCH 3/4] feat: pass the acceptable range from the calling contract through the extra data --- .../linea-ccip-gateway/contracts/TestL1.sol | 38 ++++++++++++++----- .../contracts/L1Resolver.sol | 22 +++++++++-- .../test/testL1Resolver.spec.ts | 4 +- .../contracts/EVMFetchTarget.sol | 9 +++-- .../contracts/IEVMVerifier.sol | 3 +- .../contracts/LineaSparseProofVerifier.sol | 11 +++--- 6 files changed, 61 insertions(+), 26 deletions(-) diff --git a/packages/linea-ccip-gateway/contracts/TestL1.sol b/packages/linea-ccip-gateway/contracts/TestL1.sol index 19dc1aa87..43056a472 100644 --- a/packages/linea-ccip-gateway/contracts/TestL1.sol +++ b/packages/linea-ccip-gateway/contracts/TestL1.sol @@ -8,6 +8,8 @@ import {IEVMVerifier} from "linea-state-verifier/contracts/IEVMVerifier.sol"; contract TestL1 is EVMFetchTarget { using EVMFetcher for EVMFetcher.EVMFetchRequest; + uint256 constant L2_BLOCK_RANGE_ACCEPTED = 96400; + IEVMVerifier verifier; // Slot 0 address target; @@ -19,7 +21,7 @@ contract TestL1 is EVMFetchTarget { function getLatest() public view returns (uint256) { EVMFetcher.newFetchRequest(verifier, target).getStatic(0).fetch( this.getLatestCallback.selector, - "" + abi.encode(L2_BLOCK_RANGE_ACCEPTED) ); } @@ -33,7 +35,7 @@ contract TestL1 is EVMFetchTarget { function getName() public view returns (string memory) { EVMFetcher.newFetchRequest(verifier, target).getDynamic(1).fetch( this.getNameCallback.selector, - "" + abi.encode(L2_BLOCK_RANGE_ACCEPTED) ); } @@ -49,7 +51,10 @@ contract TestL1 is EVMFetchTarget { .newFetchRequest(verifier, target) .getDynamic(3) .element(idx) - .fetch(this.getHighscorerCallback.selector, ""); + .fetch( + this.getHighscorerCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getHighscorerCallback( @@ -65,7 +70,10 @@ contract TestL1 is EVMFetchTarget { .getStatic(0) .getStatic(2) .ref(0) - .fetch(this.getLatestHighscoreCallback.selector, ""); + .fetch( + this.getLatestHighscoreCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getLatestHighscoreCallback( @@ -81,7 +89,10 @@ contract TestL1 is EVMFetchTarget { .getStatic(0) .getDynamic(3) .ref(0) - .fetch(this.getLatestHighscorerCallback.selector, ""); + .fetch( + this.getLatestHighscorerCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getLatestHighscorerCallback( @@ -98,7 +109,10 @@ contract TestL1 is EVMFetchTarget { .newFetchRequest(verifier, target) .getDynamic(4) .element(_name) - .fetch(this.getNicknameCallback.selector, ""); + .fetch( + this.getNicknameCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getNicknameCallback( @@ -114,7 +128,10 @@ contract TestL1 is EVMFetchTarget { .getDynamic(1) .getDynamic(4) .ref(0) - .fetch(this.getPrimaryNicknameCallback.selector, ""); + .fetch( + this.getPrimaryNicknameCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getPrimaryNicknameCallback( @@ -127,7 +144,7 @@ contract TestL1 is EVMFetchTarget { function getZero() public view returns (uint256) { EVMFetcher.newFetchRequest(verifier, target).getStatic(5).fetch( this.getZeroCallback.selector, - "" + abi.encode(L2_BLOCK_RANGE_ACCEPTED) ); } @@ -144,7 +161,10 @@ contract TestL1 is EVMFetchTarget { .getStatic(5) .getStatic(2) .ref(0) - .fetch(this.getZeroIndexCallback.selector, ""); + .fetch( + this.getZeroIndexCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function getZeroIndexCallback( diff --git a/packages/linea-ens-resolver/contracts/L1Resolver.sol b/packages/linea-ens-resolver/contracts/L1Resolver.sol index e8ad8c0f9..7ef3db20f 100644 --- a/packages/linea-ens-resolver/contracts/L1Resolver.sol +++ b/packages/linea-ens-resolver/contracts/L1Resolver.sol @@ -38,6 +38,8 @@ contract L1Resolver is uint256 constant VERSIONABLE_ADDRESSES_SLOT = 2; uint256 constant VERSIONABLE_HASHES_SLOT = 3; uint256 constant VERSIONABLE_TEXTS_SLOT = 10; + // To check how old is the value/proof returned and is in the acceptable range + uint256 constant L2_BLOCK_RANGE_ACCEPTED = 96400; string public graphqlUrl; event TargetSet(bytes name, address target); @@ -207,7 +209,10 @@ contract L1Resolver is .ref(0) .element(node) .element(COIN_TYPE_ETH) - .fetch(this.addrCallback.selector, ""); // recordVersions + .fetch( + this.addrCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); // recordVersions } function addrCallback( @@ -230,7 +235,10 @@ contract L1Resolver is .ref(0) .element(node) .element(coinType) - .fetch(this.addrCoinTypeCallback.selector, ""); + .fetch( + this.addrCoinTypeCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function addrCoinTypeCallback( @@ -253,7 +261,10 @@ contract L1Resolver is .ref(0) .element(node) .element(key) - .fetch(this.textCallback.selector, ""); + .fetch( + this.textCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function textCallback( @@ -274,7 +285,10 @@ contract L1Resolver is .getDynamic(VERSIONABLE_HASHES_SLOT) .ref(0) .element(node) - .fetch(this.contenthashCallback.selector, ""); + .fetch( + this.contenthashCallback.selector, + abi.encode(L2_BLOCK_RANGE_ACCEPTED) + ); } function contenthashCallback( diff --git a/packages/linea-ens-resolver/test/testL1Resolver.spec.ts b/packages/linea-ens-resolver/test/testL1Resolver.spec.ts index 5155b9044..41c9f03f2 100644 --- a/packages/linea-ens-resolver/test/testL1Resolver.spec.ts +++ b/packages/linea-ens-resolver/test/testL1Resolver.spec.ts @@ -422,7 +422,6 @@ describe("Crosschain Resolver", () => { it("should revert if the number of commands is bigger than the number of storage proofs returned by the gateway", async () => { const currentBlockNo = await rollup.currentL2BlockNumber(); const currentStateRoot = await rollup.stateRootHashes(currentBlockNo); - // Put a wrong block number await rollup.setCurrentStateRoot(blockNo, stateRoot); let proofsEncoded = AbiCoder.defaultAbiCoder().encode( [ @@ -437,7 +436,8 @@ describe("Crosschain Resolver", () => { l2ResolverAddress, commands2Test, constantsTest, - proofsEncoded + proofsEncoded, + AbiCoder.defaultAbiCoder().encode(["uint256"], [blockNo]) ); } catch (error) { expect(error.reason).to.equal( diff --git a/packages/linea-state-verifier/contracts/EVMFetchTarget.sol b/packages/linea-state-verifier/contracts/EVMFetchTarget.sol index 791a50fb9..0f4102543 100644 --- a/packages/linea-state-verifier/contracts/EVMFetchTarget.sol +++ b/packages/linea-state-verifier/contracts/EVMFetchTarget.sol @@ -27,22 +27,23 @@ abstract contract EVMFetchTarget { bytes32[] memory commands, bytes[] memory constants, bytes4 callback, - bytes memory callbackData + uint256 l2BlockRangeAccepted ) = abi.decode( extradata, - (IEVMVerifier, address, bytes32[], bytes[], bytes4, bytes) + (IEVMVerifier, address, bytes32[], bytes[], bytes4, uint256) ); bytes[] memory values = verifier.getStorageValues( addr, commands, constants, - proof + proof, + l2BlockRangeAccepted ); if (values.length != commands.length) { revert ResponseLengthMismatch(values.length, commands.length); } bytes memory ret = address(this).functionCall( - abi.encodeWithSelector(callback, values, callbackData) + abi.encodeWithSelector(callback, values, "") ); assembly { return(add(ret, 32), mload(ret)) diff --git a/packages/linea-state-verifier/contracts/IEVMVerifier.sol b/packages/linea-state-verifier/contracts/IEVMVerifier.sol index 32cfb5f3a..77d7f1bca 100644 --- a/packages/linea-state-verifier/contracts/IEVMVerifier.sol +++ b/packages/linea-state-verifier/contracts/IEVMVerifier.sol @@ -8,6 +8,7 @@ interface IEVMVerifier { address target, bytes32[] memory commands, bytes[] memory constants, - bytes memory proof + bytes memory proof, + uint256 l2BlockRangeAccepted ) external view returns (bytes[] memory values); } diff --git a/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol b/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol index d95e46961..66473f4c2 100644 --- a/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol +++ b/packages/linea-state-verifier/contracts/LineaSparseProofVerifier.sol @@ -13,8 +13,6 @@ interface IRollup { } contract LineaSparseProofVerifier is IEVMVerifier { - uint256 public constant L2_BLOCK_RANGE_ACCEPTED = 96400; - string[] public _gatewayURLs; address public _rollup; @@ -27,7 +25,8 @@ contract LineaSparseProofVerifier is IEVMVerifier { address target, bytes32[] memory commands, bytes[] memory constants, - bytes memory proof + bytes memory proof, + uint256 l2BlockRangeAccepted ) external view returns (bytes[] memory values) { ( uint256 blockNo, @@ -38,12 +37,12 @@ contract LineaSparseProofVerifier is IEVMVerifier { (uint256, AccountProofStruct, StorageProofStruct[]) ); - // Check that the L2 block number used is a recent one + // Check that the L2 block number used is a recent one and is part of the range accepted uint256 currentL2BlockNumber = IRollup(_rollup).currentL2BlockNumber(); require( - (currentL2BlockNumber <= L2_BLOCK_RANGE_ACCEPTED && + (currentL2BlockNumber <= l2BlockRangeAccepted && blockNo <= currentL2BlockNumber) || - blockNo >= currentL2BlockNumber - L2_BLOCK_RANGE_ACCEPTED, + blockNo >= currentL2BlockNumber - l2BlockRangeAccepted, "LineaSparseProofVerifier: block not in range accepted" ); From e8967d69669779817d9751b6ca4f9c3ab8aff1e0 Mon Sep 17 00:00:00 2001 From: Julink Date: Mon, 15 Jul 2024 23:13:47 +0200 Subject: [PATCH 4/4] feat: change block range accepted to 86400 --- packages/linea-ccip-gateway/contracts/TestL1.sol | 2 +- packages/linea-ens-resolver/contracts/L1Resolver.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/linea-ccip-gateway/contracts/TestL1.sol b/packages/linea-ccip-gateway/contracts/TestL1.sol index 43056a472..02b7aaddd 100644 --- a/packages/linea-ccip-gateway/contracts/TestL1.sol +++ b/packages/linea-ccip-gateway/contracts/TestL1.sol @@ -8,7 +8,7 @@ import {IEVMVerifier} from "linea-state-verifier/contracts/IEVMVerifier.sol"; contract TestL1 is EVMFetchTarget { using EVMFetcher for EVMFetcher.EVMFetchRequest; - uint256 constant L2_BLOCK_RANGE_ACCEPTED = 96400; + uint256 constant L2_BLOCK_RANGE_ACCEPTED = 86400; IEVMVerifier verifier; // Slot 0 address target; diff --git a/packages/linea-ens-resolver/contracts/L1Resolver.sol b/packages/linea-ens-resolver/contracts/L1Resolver.sol index 7ef3db20f..0b0a42b77 100644 --- a/packages/linea-ens-resolver/contracts/L1Resolver.sol +++ b/packages/linea-ens-resolver/contracts/L1Resolver.sol @@ -39,7 +39,7 @@ contract L1Resolver is uint256 constant VERSIONABLE_HASHES_SLOT = 3; uint256 constant VERSIONABLE_TEXTS_SLOT = 10; // To check how old is the value/proof returned and is in the acceptable range - uint256 constant L2_BLOCK_RANGE_ACCEPTED = 96400; + uint256 constant L2_BLOCK_RANGE_ACCEPTED = 86400; string public graphqlUrl; event TargetSet(bytes name, address target);