Skip to content

Commit

Permalink
Merge pull request #197 from Consensys/feat/update-state-verifier-blo…
Browse files Browse the repository at this point in the history
…ck-range-accepted

feat: make getStorageValues more flexible to accept the most recent L2 finalized blocks
  • Loading branch information
Julink-eth authored Jul 16, 2024
2 parents 1e3b973 + e8967d6 commit ce80d64
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 29 deletions.
14 changes: 14 additions & 0 deletions packages/linea-ccip-gateway/contracts/RollupMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
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;

constructor(uint256 _currentL2BlockNumber, bytes32 _currentStateRootHash) {
currentL2BlockNumber = _currentL2BlockNumber;
stateRootHashes[currentL2BlockNumber] = _currentStateRootHash;

emit DataFinalized(
_currentL2BlockNumber,
_currentStateRootHash,
_currentStateRootHash,
false
);
}
}
38 changes: 29 additions & 9 deletions packages/linea-ccip-gateway/contracts/TestL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 86400;

IEVMVerifier verifier; // Slot 0
address target;

Expand All @@ -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)
);
}

Expand All @@ -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)
);
}

Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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)
);
}

Expand All @@ -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(
Expand Down
40 changes: 37 additions & 3 deletions packages/linea-ccip-gateway/src/L2ProofService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const currentL2BlockNumberSig =
export class L2ProofService implements IProofService<L2ProvableBlock> {
private readonly rollup: Contract;
private readonly helper: EVMProofHelper;
private readonly l1Provider: JsonRpcProvider;

constructor(
providerL1: JsonRpcProvider,
Expand All @@ -36,16 +37,49 @@ export class L2ProofService implements IProofService<L2ProvableBlock> {
currentL2BlockNumberIface,
providerL1
);
this.l1Provider = providerL1;
}

/**
* @dev Returns an object representing a block whose state can be proven on L1.
*/
async getProvableBlock(): Promise<number> {
try {
const lastBlockFinalized = await this.rollup.currentL2BlockNumber();
if (!lastBlockFinalized) throw new Error("No block found");
return lastBlockFinalized;
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,
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;
Expand Down
22 changes: 18 additions & 4 deletions packages/linea-ens-resolver/contracts/L1Resolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 86400;
string public graphqlUrl;

event TargetSet(bytes name, address target);
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down
21 changes: 21 additions & 0 deletions packages/linea-ens-resolver/contracts/RollupMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@
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;

constructor(uint256 _currentL2BlockNumber, bytes32 _currentStateRootHash) {
currentL2BlockNumber = _currentL2BlockNumber;
stateRootHashes[currentL2BlockNumber] = _currentStateRootHash;

emit DataFinalized(
_currentL2BlockNumber,
_currentStateRootHash,
_currentStateRootHash,
false
);
}

function setCurrentStateRoot(
Expand All @@ -17,5 +31,12 @@ contract RollupMock {
) external {
currentL2BlockNumber = _currentL2BlockNumber;
stateRootHashes[currentL2BlockNumber] = _currentStateRootHash;

emit DataFinalized(
_currentL2BlockNumber,
_currentStateRootHash,
_currentStateRootHash,
false
);
}
}
8 changes: 4 additions & 4 deletions packages/linea-ens-resolver/test/testL1Resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand All @@ -437,7 +436,8 @@ describe("Crosschain Resolver", () => {
l2ResolverAddress,
commands2Test,
constantsTest,
proofsEncoded
proofsEncoded,
AbiCoder.defaultAbiCoder().encode(["uint256"], [blockNo])
);
} catch (error) {
expect(error.reason).to.equal(
Expand All @@ -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",
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions packages/linea-state-verifier/contracts/EVMFetchTarget.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion packages/linea-state-verifier/contracts/IEVMVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,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,
Expand All @@ -36,10 +37,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 and is part of the range accepted
uint256 currentL2BlockNumber = IRollup(_rollup).currentL2BlockNumber();
require(
blockNo == IRollup(_rollup).currentL2BlockNumber(),
"LineaSparseProofVerifier: not latest finalized block"
(currentL2BlockNumber <= l2BlockRangeAccepted &&
blockNo <= currentL2BlockNumber) ||
blockNo >= currentL2BlockNumber - l2BlockRangeAccepted,
"LineaSparseProofVerifier: block not in range accepted"
);

bytes32 stateRoot = IRollup(_rollup).stateRootHashes(blockNo);
Expand Down

0 comments on commit ce80d64

Please sign in to comment.