-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
22dcaf0
commit 21093f7
Showing
6 changed files
with
983 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { describe, it, expect } from 'vitest'; | ||
import { Address, createPublicClient, http } from 'viem'; | ||
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; | ||
|
||
import { nitroTestnodeL2 } from './chains'; | ||
import { sequencerInboxActions } from './decorators/sequencerInboxActions'; | ||
import { getInformationFromTestnode, getNitroTestnodePrivateKeyAccounts } from './testHelpers'; | ||
import { getBatchPosters } from './getBatchPosters'; | ||
|
||
const { l3RollupOwner } = getNitroTestnodePrivateKeyAccounts(); | ||
const { l3Rollup, l3UpgradeExecutor, l3SequencerInbox } = getInformationFromTestnode(); | ||
|
||
const client = createPublicClient({ | ||
chain: nitroTestnodeL2, | ||
transport: http(), | ||
}).extend( | ||
sequencerInboxActions({ | ||
sequencerInbox: l3SequencerInbox, | ||
}), | ||
); | ||
|
||
async function setBatchPoster(batchPoster: Address, state: boolean) { | ||
const tx = await client.sequencerInboxPrepareTransactionRequest({ | ||
functionName: 'setIsBatchPoster', | ||
args: [batchPoster, state], | ||
account: l3RollupOwner.address, | ||
upgradeExecutor: l3UpgradeExecutor, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
|
||
const txHash = await client.sendRawTransaction({ | ||
serializedTransaction: await l3RollupOwner.signTransaction(tx), | ||
}); | ||
|
||
await client.waitForTransactionReceipt({ | ||
hash: txHash, | ||
}); | ||
} | ||
|
||
// Tests can be enabled once we run one node per integration test | ||
describe.skip('successfully get batch posters', () => { | ||
it('when disabling the same batch posters multiple time', async () => { | ||
const randomAccount = privateKeyToAccount(generatePrivateKey()).address; | ||
|
||
const { isAccurate: isAccurateInitially, batchPosters: initialBatchPosters } = | ||
await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
|
||
// By default, chains from nitro testnode has 1 batch poster | ||
expect(initialBatchPosters).toHaveLength(1); | ||
expect(isAccurateInitially).toBeTruthy(); | ||
|
||
await setBatchPoster(randomAccount, false); | ||
await setBatchPoster(randomAccount, false); | ||
|
||
const { isAccurate: isStillAccurate, batchPosters: newBatchPosters } = await getBatchPosters( | ||
client, | ||
{ | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}, | ||
); | ||
// Setting the same batch poster multiple time to false doesn't add new batch posters | ||
expect(newBatchPosters).toEqual(initialBatchPosters); | ||
expect(isStillAccurate).toBeTruthy(); | ||
|
||
await setBatchPoster(randomAccount, true); | ||
const { batchPosters, isAccurate } = await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
|
||
expect(batchPosters).toEqual(initialBatchPosters.concat(randomAccount)); | ||
expect(isAccurate).toBeTruthy(); | ||
|
||
// Reset state for future tests | ||
await setBatchPoster(randomAccount, false); | ||
const { isAccurate: isAccurateFinal, batchPosters: batchPostersFinal } = await getBatchPosters( | ||
client, | ||
{ | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}, | ||
); | ||
expect(batchPostersFinal).toEqual(initialBatchPosters); | ||
expect(isAccurateFinal).toBeTruthy(); | ||
}); | ||
|
||
it('when enabling the same batch poster multiple time', async () => { | ||
const randomAccount = privateKeyToAccount(generatePrivateKey()).address; | ||
|
||
const { isAccurate: isAccurateInitially, batchPosters: initialBatchPosters } = | ||
await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
// By default, chains from nitro testnode has 1 batch poster | ||
expect(initialBatchPosters).toHaveLength(1); | ||
expect(isAccurateInitially).toBeTruthy(); | ||
|
||
await setBatchPoster(randomAccount, true); | ||
await setBatchPoster(randomAccount, true); | ||
const { isAccurate: isStillAccurate, batchPosters: newBatchPosters } = await getBatchPosters( | ||
client, | ||
{ | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}, | ||
); | ||
|
||
expect(newBatchPosters).toEqual(initialBatchPosters.concat(randomAccount)); | ||
expect(isStillAccurate).toBeTruthy(); | ||
|
||
// Reset state for futures tests | ||
await setBatchPoster(randomAccount, false); | ||
const { batchPosters, isAccurate } = await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
expect(batchPosters).toEqual(initialBatchPosters); | ||
expect(isAccurate).toBeTruthy(); | ||
}); | ||
|
||
it('when adding an existing batch poster', async () => { | ||
const { isAccurate: isAccurateInitially, batchPosters: initialBatchPosters } = | ||
await getBatchPosters(client, { rollup: l3Rollup, sequencerInbox: l3SequencerInbox }); | ||
expect(initialBatchPosters).toHaveLength(1); | ||
expect(isAccurateInitially).toBeTruthy(); | ||
|
||
const firstBatchPoster = initialBatchPosters[0]; | ||
await setBatchPoster(firstBatchPoster, true); | ||
|
||
const { isAccurate, batchPosters } = await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
expect(batchPosters).toEqual(initialBatchPosters); | ||
expect(isAccurate).toBeTruthy(); | ||
}); | ||
|
||
it('when removing an existing batch poster', async () => { | ||
const { isAccurate: isAccurateInitially, batchPosters: initialBatchPosters } = | ||
await getBatchPosters(client, { rollup: l3Rollup, sequencerInbox: l3SequencerInbox }); | ||
expect(initialBatchPosters).toHaveLength(1); | ||
expect(isAccurateInitially).toBeTruthy(); | ||
|
||
const lastBatchPoster = initialBatchPosters[initialBatchPosters.length - 1]; | ||
await setBatchPoster(lastBatchPoster, false); | ||
const { isAccurate, batchPosters } = await getBatchPosters(client, { | ||
rollup: l3Rollup, | ||
sequencerInbox: l3SequencerInbox, | ||
}); | ||
expect(batchPosters).toEqual(initialBatchPosters.slice(0, -1)); | ||
expect(isAccurate).toBeTruthy(); | ||
|
||
await setBatchPoster(lastBatchPoster, true); | ||
const { isAccurate: isAccurateFinal, batchPosters: batchPostersFinal } = await getBatchPosters( | ||
client, | ||
{ rollup: l3Rollup, sequencerInbox: l3SequencerInbox }, | ||
); | ||
expect(batchPostersFinal).toEqual(initialBatchPosters); | ||
expect(isAccurateFinal).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { | ||
Address, | ||
Chain, | ||
Hex, | ||
PublicClient, | ||
Transport, | ||
decodeFunctionData, | ||
getAbiItem, | ||
getFunctionSelector, | ||
} from 'viem'; | ||
import { rollupCreator, upgradeExecutor } from './contracts'; | ||
import { safeL2ABI, sequencerInboxABI } from './abi'; | ||
import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash'; | ||
|
||
const createRollupABI = getAbiItem({ abi: rollupCreator.abi, name: 'createRollup' }); | ||
const createRollupFunctionSelector = getFunctionSelector(createRollupABI); | ||
|
||
const setIsBatchPosterABI = getAbiItem({ abi: sequencerInboxABI, name: 'setIsBatchPoster' }); | ||
const setIsBatchPosterFunctionSelector = getFunctionSelector(setIsBatchPosterABI); | ||
|
||
const executeCallABI = getAbiItem({ abi: upgradeExecutor.abi, name: 'executeCall' }); | ||
const upgradeExecutorExecuteCallFunctionSelector = getFunctionSelector(executeCallABI); | ||
|
||
const execTransactionABI = getAbiItem({ abi: safeL2ABI, name: 'execTransaction' }); | ||
const safeL2FunctionSelector = getFunctionSelector(execTransactionABI); | ||
|
||
const ownerFunctionCalledEventAbi = getAbiItem({ | ||
abi: sequencerInboxABI, | ||
name: 'OwnerFunctionCalled', | ||
}); | ||
|
||
function getBatchPostersFromFunctionData< | ||
TAbi extends (typeof createRollupABI)[] | (typeof setIsBatchPosterABI)[], | ||
>({ abi, data }: { abi: TAbi; data: Hex }) { | ||
const { args } = decodeFunctionData({ | ||
abi, | ||
data, | ||
}); | ||
return args; | ||
} | ||
|
||
function updateAccumulator(acc: Set<Address>, input: Hex) { | ||
const [batchPoster, isAdd] = getBatchPostersFromFunctionData({ | ||
abi: [setIsBatchPosterABI], | ||
data: input, | ||
}); | ||
|
||
if (isAdd) { | ||
acc.add(batchPoster); | ||
} else { | ||
acc.delete(batchPoster); | ||
} | ||
|
||
return acc; | ||
} | ||
|
||
export type GetBatchPostersParams = { | ||
/** Address of the rollup we're getting list of batch posters from */ | ||
rollup: Address; | ||
/** Address of the sequencerInbox we're getting logs from */ | ||
sequencerInbox: Address; | ||
}; | ||
export type GetBatchPostersReturnType = { | ||
/** | ||
* If logs contain unknown signature, batch posters list might: | ||
* - contain false positives (batch posters that were removed, but returned as batch poster) | ||
* - contain false negatives (batch posters that were added, but not present in the list) | ||
*/ | ||
isAccurate: boolean; | ||
/** List of batch posters for the given rollup */ | ||
batchPosters: Address[]; | ||
}; | ||
|
||
/** | ||
* | ||
* @param {PublicClient} publicClient - The chain Viem Public Client | ||
* @param {GetBatchPostersParams} GetBatchPostersParams {@link GetBatchPostersParams} | ||
* | ||
* @returns Promise<{@link GetBatchPostersReturnType}> | ||
* | ||
* @remarks Batch posters list is not guaranteed to be exhaustive if the `isAccurate` flag is false. | ||
* It might contain false positive (batch posters that were removed, but returned as batch poster) | ||
* or false negative (batch posters that were added, but not present in the list) | ||
* | ||
* @example | ||
* const { isAccurate, batchPosters } = getBatchPosters(client, { | ||
* rollup: '0xc47dacfbaa80bd9d8112f4e8069482c2a3221336', | ||
* sequencerInbox: '0x995a9d3ca121D48d21087eDE20bc8acb2398c8B1' | ||
* }); | ||
* | ||
* if (isAccurate) { | ||
* // batch posters were all fetched properly | ||
* } else { | ||
* // batch posters list is not guaranteed to be accurate | ||
* } | ||
*/ | ||
export async function getBatchPosters<TChain extends Chain | undefined>( | ||
publicClient: PublicClient<Transport, TChain>, | ||
{ rollup, sequencerInbox }: GetBatchPostersParams, | ||
): Promise<GetBatchPostersReturnType> { | ||
let blockNumber: bigint | 'earliest'; | ||
let createRollupTransactionHash: Address | null = null; | ||
try { | ||
createRollupTransactionHash = await createRollupFetchTransactionHash({ | ||
rollup, | ||
publicClient, | ||
}); | ||
const receipt = await publicClient.waitForTransactionReceipt({ | ||
hash: createRollupTransactionHash, | ||
}); | ||
blockNumber = receipt.blockNumber; | ||
} catch (e) { | ||
blockNumber = 'earliest'; | ||
} | ||
|
||
const sequencerInboxEvents = await publicClient.getLogs({ | ||
address: sequencerInbox, | ||
event: ownerFunctionCalledEventAbi, | ||
args: { id: 1n }, | ||
fromBlock: blockNumber, | ||
toBlock: 'latest', | ||
}); | ||
|
||
const events = createRollupTransactionHash | ||
? [{ transactionHash: createRollupTransactionHash }, ...sequencerInboxEvents] | ||
: sequencerInboxEvents; | ||
const txs = await Promise.all( | ||
events.map((event) => | ||
publicClient.getTransaction({ | ||
hash: event.transactionHash, | ||
}), | ||
), | ||
); | ||
|
||
let isAccurate = true; | ||
const batchPosters = txs.reduce((acc, tx) => { | ||
const txSelectedFunction = tx.input.slice(0, 10); | ||
|
||
switch (txSelectedFunction) { | ||
case createRollupFunctionSelector: { | ||
const [{ batchPoster }] = getBatchPostersFromFunctionData({ | ||
abi: [createRollupABI], | ||
data: tx.input, | ||
}); | ||
|
||
return new Set([...acc, batchPoster]); | ||
} | ||
case setIsBatchPosterFunctionSelector: { | ||
return updateAccumulator(acc, tx.input); | ||
} | ||
case upgradeExecutorExecuteCallFunctionSelector: { | ||
const { args: executeCallCalldata } = decodeFunctionData({ | ||
abi: [executeCallABI], | ||
data: tx.input, | ||
}); | ||
return updateAccumulator(acc, executeCallCalldata[1]); | ||
} | ||
case safeL2FunctionSelector: { | ||
const { args: execTransactionCalldata } = decodeFunctionData({ | ||
abi: [execTransactionABI], | ||
data: tx.input, | ||
}); | ||
const { args: executeCallCalldata } = decodeFunctionData({ | ||
abi: [executeCallABI], | ||
data: execTransactionCalldata[2], | ||
}); | ||
return updateAccumulator(acc, executeCallCalldata[1]); | ||
} | ||
default: { | ||
console.warn(`[getBatchPosters] unknown 4bytes, tx id: ${tx.hash}`); | ||
isAccurate = false; | ||
return acc; | ||
} | ||
} | ||
}, new Set<Address>()); | ||
|
||
return { | ||
isAccurate, | ||
batchPosters: [...batchPosters], | ||
}; | ||
} |
Oops, something went wrong.