Skip to content

Commit

Permalink
make unfinalized op-fault dodge carry and check rootClaim
Browse files Browse the repository at this point in the history
  • Loading branch information
adraffy committed Dec 2, 2024
1 parent 67d92e1 commit 11707b1
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 23 deletions.
23 changes: 16 additions & 7 deletions src/op/AbstractOPRollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
BigNumberish,
ProofSequence,
ProofSequenceV1,
HexString32,
} from '../types.js';
import {
AbstractRollup,
Expand All @@ -11,25 +12,33 @@ import {
} from '../rollup.js';
import { EthProver } from '../eth/EthProver.js';
import { ZeroHash } from 'ethers/constants';
import { keccak256 } from 'ethers/crypto';
import { ABI_CODER } from '../utils.js';

const OutputRootProofType = `tuple(
const OutputRootProofType = `(
bytes32 version,
bytes32 stateRoot,
bytes32 messagePasserStorageRoot,
bytes32 latestBlockhash
)`;

function outputRootProofTuple(commit: OPCommit) {
return [ZeroHash, commit.stateRoot, commit.passerRoot, commit.blockHash];
}

// same as lib/optimism/packages/contract-bedrock/src/libraries/Hashing.sol
export function hashOutputRootProof(commit: OPCommit): HexString32 {
return keccak256(
ABI_CODER.encode([OutputRootProofType], [outputRootProofTuple(commit)])
);
}

export type OPCommit = RollupCommit<EthProver> & {
readonly blockHash: HexString;
readonly stateRoot: HexString;
readonly passerRoot: HexString;
};

function outputRootProofTuple(commit: OPCommit) {
return [ZeroHash, commit.stateRoot, commit.passerRoot, commit.blockHash];
}

const L2ToL1MessagePasser = '0x4200000000000000000000000000000000000016';

export abstract class AbstractOPRollup
Expand All @@ -53,7 +62,7 @@ export abstract class AbstractOPRollup
}
override encodeWitness(commit: OPCommit, proofSeq: ProofSequence) {
return ABI_CODER.encode(
[`tuple(uint256, ${OutputRootProofType}, bytes[], bytes)`],
[`(uint256, ${OutputRootProofType}, bytes[], bytes)`],
[
[
commit.index,
Expand All @@ -66,7 +75,7 @@ export abstract class AbstractOPRollup
}
encodeWitnessV1(commit: OPCommit, proofSeq: ProofSequenceV1) {
return ABI_CODER.encode(
[`tuple(uint256, ${OutputRootProofType})`, 'tuple(bytes, bytes[])'],
[`(uint256, ${OutputRootProofType})`, 'tuple(bytes, bytes[])'],
[
[commit.index, outputRootProofTuple(commit)],
[proofSeq.accountProof, proofSeq.storageProofs],
Expand Down
71 changes: 55 additions & 16 deletions src/op/OPFaultRollup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import type { RollupDeployment } from '../rollup.js';
import type { HexAddress, ProviderPair } from '../types.js';
import type { HexAddress, HexString32, ProviderPair } from '../types.js';
import { Contract } from 'ethers/contract';
import { PORTAL_ABI, GAME_FINDER_ABI } from './types.js';
import { PORTAL_ABI, GAME_FINDER_ABI, GAME_ABI } from './types.js';
import { CHAINS } from '../chains.js';
import { AbstractOPRollup, type OPCommit } from './AbstractOPRollup.js';
import {
AbstractOPRollup,
hashOutputRootProof,
type OPCommit,
} from './AbstractOPRollup.js';

// https://docs.optimism.io/chain/differences
// https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html
Expand Down Expand Up @@ -113,7 +117,31 @@ export class OPFaultRollup extends AbstractOPRollup {
blockTag: this.latestBlockTag,
});
}

private async _ensureRootClaim(index: bigint) {
// dodge canary by requiring a valid root claim
// finalized claims are assumed valid
if (this.unfinalized) {
for (;;) {
try {
await this.fetchCommit(index);
break;
} catch (err) {
// NOTE: this could fail for a variety of reasons
// so we can't just catch "invalid root claim"
// canary often has invalid block <== likely triggers first
// canary has invalid time
// canary has invalid root claim
index = await this.GameFinder.findGameIndex(
this.OptimismPortal.target,
this.minAgeSec,
this.gameTypeBitMask,
index
);
}
}
}
return index;
}
override async fetchLatestCommitIndex(): Promise<bigint> {
// the primary assumption is that the anchor root is the finalized state
// however, this is strangely conditional on the gameType
Expand All @@ -123,22 +151,26 @@ export class OPFaultRollup extends AbstractOPRollup {
// 20240820: correctly handles the aug 16 respectedGameType change
// this should be simplified in the future once there is a better policy
// 20240822: once again uses a helper contract to reduce rpc burden
return this.GameFinder.findGameIndex(
this.OptimismPortal.target,
this.minAgeSec,
this.gameTypeBitMask,
0,
{ blockTag: this.latestBlockTag }
return this._ensureRootClaim(
await this.GameFinder.findGameIndex(
this.OptimismPortal.target,
this.minAgeSec,
this.gameTypeBitMask,
0, // most recent game
{ blockTag: this.latestBlockTag }
)
);
}
protected override async _fetchParentCommitIndex(
commit: OPCommit
): Promise<bigint> {
return this.GameFinder.findGameIndex(
this.OptimismPortal.target,
this.minAgeSec,
this.gameTypeBitMask,
commit.index
return this._ensureRootClaim(
await this.GameFinder.findGameIndex(
this.OptimismPortal.target,
this.minAgeSec,
this.gameTypeBitMask,
commit.index
)
);
}
protected override async _fetchCommit(index: bigint) {
Expand All @@ -149,7 +181,14 @@ export class OPFaultRollup extends AbstractOPRollup {
index
);
if (!game.l2BlockNumber) throw new Error('invalid game');
return this.createCommit(index, game.l2BlockNumber);
const commit = await this.createCommit(index, game.l2BlockNumber);
if (this.unfinalized) {
const gameProxy = new Contract(game.gameProxy, GAME_ABI, this.provider1);
const expected: HexString32 = await gameProxy.rootClaim();
const computed = hashOutputRootProof(commit);
if (expected !== computed) throw new Error(`invalid root claim`);
}
return commit;
}

override windowFromSec(sec: number): number {
Expand Down
4 changes: 4 additions & 0 deletions src/op/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const GAME_FINDER_ABI = new Interface([
)`,
]);

export const GAME_ABI = new Interface([
`function rootClaim() view returns (bytes32)`,
]);

export const L1_BLOCK_ABI = new Interface([
`function number() view returns (uint256)`,
//`function hash() view returns (bytes32)`,
Expand Down

0 comments on commit 11707b1

Please sign in to comment.