From a99bbd6d00afe75a1dca2205c37d7c84f4327380 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sat, 6 Jan 2024 16:43:53 +0100 Subject: [PATCH 01/17] initial commit --- packages/optimism-decoder/src/analyze.ts | 2 + packages/optimism-decoder/src/decode.ts | 139 ++++++++++++++--------- 2 files changed, 86 insertions(+), 55 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 9b8632ac..fd846779 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -6,6 +6,7 @@ const ctcMapping: Record = { '0x56a76bcC92361f6DF8D75476feD8843EdC70e1C9': 'Metis', '0x6A1DB7d799FBA381F2a518cA859ED30cB8E1d41a': 'Metis 2.0', '0xfBd2541e316948B259264c02f370eD088E04c3Db': 'Boba Network', + '0x5f7f7f6DB967F0ef10BdA0678964DBA185d16c50': 'Lyra', } export async function analyzeTransaction( @@ -14,6 +15,7 @@ export async function analyzeTransaction( ) { const tx = await provider.getTransaction(txHash) const project = ctcMapping[tx.to ?? ''] ?? 'Unknown' + console.log('Tx submits data to', tx.to, 'hence it is', project) return { data: tx.data, diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 6611e9ad..ec4004fc 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -24,70 +24,99 @@ export async function decodeSequencerBatch( data: string, fourBytesApi: FourBytesApi, ): Promise { - console.log('Decoding', kind, 'L1 Sequencer transaction batch...') + console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') let reader = new BufferReader(Buffer.from(data.slice(2), 'hex')) - const methodName = reader.readBytes(4).toString('hex') - console.log('MethodName:', methodName) + if (kind === 'Lyra') { + const version = reader.readBytes(1).toString('hex') + console.log('Version:', version) + const channelId = reader.readBytes(16).toString('hex') + console.log('ChannelId:', channelId) + const frame_number = reader.readU16BE() + console.log('Frame Number:', frame_number) + const frame_data_length = reader.readU32BE() + console.log('Frame Data Length:', frame_data_length) + console.log(reader.left()) + const bytes = reader.readBytes(reader.left() - 1) + const inflated = zlib.inflateSync(bytes) + reader = new BufferReader(inflated) + const decompressedBytes = reader.readBytes(reader.left()) + console.log(add0x(decompressedBytes.toString('hex'))) + const decoded = ethers.utils.RLP.decode( + // TODO: why this is failing ???? + add0x(decompressedBytes.toString('hex')), + ) + console.log(decoded) + } else { + const methodName = reader.readBytes(4).toString('hex') + console.log('MethodName:', methodName) - if (kind === 'Metis' || kind === 'Metis 2.0') { - const chainId = reader.readBytes(32).toString('hex') - console.log('ChainId:', chainId) - } - const shouldStartAtElement = reader.readU40BE() - const totalElementsToAppend = reader.readU24BE() - const contextCount = reader.readU24BE() + if (kind === 'Metis' || kind === 'Metis 2.0') { + const chainId = reader.readBytes(32).toString('hex') + console.log('ChainId:', chainId) + } + const shouldStartAtElement = reader.readU40BE() + const totalElementsToAppend = reader.readU24BE() + const contextCount = reader.readU24BE() - console.log('Should start at Element:', shouldStartAtElement) - console.log('Total Elements to Append:', totalElementsToAppend) - console.log('contextCount:', contextCount) + console.log('Should start at Element:', shouldStartAtElement) + console.log('Total Elements to Append:', totalElementsToAppend) + console.log('contextCount:', contextCount) - const contexts = [] - for (let i = 0; i < contextCount; i++) { - const sequencerTxCount = reader.readU24BE() - const queueTxCount = reader.readU24BE() - const timestamp = reader.readU40BE() - const blockNumber = reader.readU40BE() - contexts.push({ - sequencerTxCount, - queueTxCount, - timestamp, - blockNumber, - }) - console.log(sequencerTxCount, queueTxCount, timestamp, blockNumber) - } + const contexts = [] + for (let i = 0; i < contextCount; i++) { + const sequencerTxCount = reader.readU24BE() + const queueTxCount = reader.readU24BE() + const timestamp = reader.readU40BE() + const blockNumber = reader.readU40BE() + contexts.push({ + sequencerTxCount, + queueTxCount, + timestamp, + blockNumber, + }) + console.log(sequencerTxCount, queueTxCount, timestamp, blockNumber) + } - if (contexts[0].blockNumber === 0 && kind === 'Optimism OVM 2.0') { - console.log( - 'Block number = 0 ? Transactions are compressed, nice.... Decompressing....', - ) - contexts.slice(1) // remove dummy context that indicates compressed transaction data - const bytes = reader.readBytes(reader.left()) - const inflated = zlib.inflateSync(bytes) - reader = new BufferReader(inflated) - } + if (contexts[0].blockNumber === 0 && kind === 'Optimism OVM 2.0') { + console.log( + 'Block number = 0 ? Transactions are compressed, nice.... Decompressing....', + ) + contexts.slice(1) // remove dummy context that indicates compressed transaction data + const bytes = reader.readBytes(reader.left()) + const inflated = zlib.inflateSync(bytes) + reader = new BufferReader(inflated) + } - const transactions = [] - for (const context of contexts) { - console.log('Block:', context.blockNumber, 'Timestamp:', context.timestamp) - for (let i = 0; i < context.sequencerTxCount; i++) { - const size = reader.readU24BE() - const raw = reader.readBytes(size).toString('hex') - const parsed = ethers.utils.parseTransaction(add0x(raw)) - const methodHash = parsed.data.slice(0, 10) - const methodSignature = await fourBytesApi.getMethodSignature(methodHash) - transactions.push(add0x(raw)) - console.log(' ', trimLong(add0x(raw)), methodHash, methodSignature) + const transactions = [] + for (const context of contexts) { + console.log( + 'Block:', + context.blockNumber, + 'Timestamp:', + context.timestamp, + ) + for (let i = 0; i < context.sequencerTxCount; i++) { + const size = reader.readU24BE() + const raw = reader.readBytes(size).toString('hex') + const parsed = ethers.utils.parseTransaction(add0x(raw)) + const methodHash = parsed.data.slice(0, 10) + const methodSignature = await fourBytesApi.getMethodSignature( + methodHash, + ) + transactions.push(add0x(raw)) + console.log(' ', trimLong(add0x(raw)), methodHash, methodSignature) + } } - } - console.log('Decoded', transactions.length, 'transactions') - console.log('Done decoding...') + console.log('Decoded', transactions.length, 'transactions') + console.log('Done decoding...') - return { - shouldStartAtElement, - totalElementsToAppend, - contexts, - transactions, + return { + shouldStartAtElement, + totalElementsToAppend, + contexts, + transactions, + } } } From 28884829f7908aff0b7f560d94a0e6efd84f5365 Mon Sep 17 00:00:00 2001 From: Luca Donno Date: Sat, 6 Jan 2024 18:12:38 +0100 Subject: [PATCH 02/17] make script more robust --- packages/optimism-decoder/src/decode.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index ec4004fc..5a3780f7 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import assert from 'assert' import { BufferReader } from 'bufio' import { ethers } from 'ethers' import zlib from 'zlib' @@ -23,7 +28,7 @@ export async function decodeSequencerBatch( kind: string, data: string, fourBytesApi: FourBytesApi, -): Promise { +): Promise { console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') let reader = new BufferReader(Buffer.from(data.slice(2), 'hex')) @@ -36,9 +41,15 @@ export async function decodeSequencerBatch( console.log('Frame Number:', frame_number) const frame_data_length = reader.readU32BE() console.log('Frame Data Length:', frame_data_length) - console.log(reader.left()) - const bytes = reader.readBytes(reader.left() - 1) + // console.log(reader.left()) + const bytes = reader.readBytes(frame_data_length) + const is_last = reader.readBytes(1).toString('hex') + assert(is_last === '01' || is_last === '00') + console.log('Is Last:', is_last === '01') const inflated = zlib.inflateSync(bytes) + + // ----- reading decompressed data ----- + reader = new BufferReader(inflated) const decompressedBytes = reader.readBytes(reader.left()) console.log(add0x(decompressedBytes.toString('hex'))) From 9bbc90902806c3009326c0bac61c5ae71f875add Mon Sep 17 00:00:00 2001 From: Luca Donno Date: Sat, 6 Jan 2024 20:38:24 +0100 Subject: [PATCH 03/17] decode batches --- packages/optimism-decoder/src/decode.ts | 41 +++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 5a3780f7..a7cc54ac 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -9,6 +9,7 @@ import zlib from 'zlib' import { FourBytesApi } from './FourBytesApi' import { add0x, trimLong } from './utils' +import { decode } from 'punycode' interface BatchContext { sequencerTxCount: number @@ -52,12 +53,40 @@ export async function decodeSequencerBatch( reader = new BufferReader(inflated) const decompressedBytes = reader.readBytes(reader.left()) - console.log(add0x(decompressedBytes.toString('hex'))) - const decoded = ethers.utils.RLP.decode( - // TODO: why this is failing ???? - add0x(decompressedBytes.toString('hex')), - ) - console.log(decoded) + // console.log(add0x(decompressedBytes.toString('hex'))) + + const totalLength = decompressedBytes.toString('hex').length / 2 // we do /2 because we are counting bytes + const lengthBytes = ethers.utils.hexlify(totalLength).slice(2) + console.log('Length Bytes:', lengthBytes) + const lengthBytesLength = lengthBytes.length / 2 + console.log('Length Bytes Length:', lengthBytesLength) + const lengthByte = 0xf7 + lengthBytesLength + console.log('Length Byte:', lengthByte) + const lengthByteHex = ethers.utils.hexlify(lengthByte) + console.log('Length Byte Hex:', lengthByteHex) + const concatenatedWithLength = + lengthByteHex + + lengthBytes + + (decompressedBytes.toString('hex') as string) + //console.log(concatenatedWithLength) + const decoded = ethers.utils.RLP.decode(concatenatedWithLength) + //console.log(decoded) + + const batches = [] + for (const batch of decoded) { + const batchHexWithout00 = batch.slice(4) + const decodedBatch = ethers.utils.RLP.decode(add0x(batchHexWithout00)) + + if (decodedBatch[decodedBatch.length - 1].length !== 0) { + const txs = decodedBatch[decodedBatch.length - 1][0] + //console.log('txs:', txs) + const transactions = ethers.utils.RLP.decode(add0x(txs.slice(4))) + //console.log('transactions:', transactions) + decodedBatch[decodedBatch.length - 1] = transactions + } + batches.push(decodedBatch) + } + console.log(batches[0]) } else { const methodName = reader.readBytes(4).toString('hex') console.log('MethodName:', methodName) From 24783ec89d9ff265a333fde7bda4a02fd7be6681 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 09:54:47 +0100 Subject: [PATCH 04/17] refactor --- packages/optimism-decoder/src/analyze.ts | 17 ++- packages/optimism-decoder/src/decode.ts | 125 ++++++++++++----------- packages/optimism-decoder/src/run.ts | 8 +- 3 files changed, 85 insertions(+), 65 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index fd846779..33534e82 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -9,16 +9,31 @@ const ctcMapping: Record = { '0x5f7f7f6DB967F0ef10BdA0678964DBA185d16c50': 'Lyra', } +const typeMapping: Record = { + 'Lyra': 'OpStack', + 'Boba Network': 'OVM 2.0', + 'Optimism OVM 1.0': 'OVM 1.0', +} + export async function analyzeTransaction( provider: providers.Provider, txHash: string, ) { const tx = await provider.getTransaction(txHash) const project = ctcMapping[tx.to ?? ''] ?? 'Unknown' - console.log('Tx submits data to', tx.to, 'hence it is', project) + const kind = typeMapping[project ?? ''] ?? 'Unknown' + console.log( + 'Tx submits data to', + tx.to, + 'hence it is', + project, + 'of kind', + kind, + ) return { data: tx.data, project, + kind, } } diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index a7cc54ac..c81c494e 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -25,11 +25,11 @@ interface AppendSequencerBatchParams { transactions: string[] // total_size_bytes[], total_size_bytes[] } -export async function decodeSequencerBatch( +export async function decodeOpStackSequencerBatch( kind: string, data: string, fourBytesApi: FourBytesApi, -): Promise { +) { console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') let reader = new BufferReader(Buffer.from(data.slice(2), 'hex')) @@ -73,6 +73,7 @@ export async function decodeSequencerBatch( //console.log(decoded) const batches = [] + console.log('Decoding', decoded.length, 'batches') for (const batch of decoded) { const batchHexWithout00 = batch.slice(4) const decodedBatch = ethers.utils.RLP.decode(add0x(batchHexWithout00)) @@ -86,67 +87,69 @@ export async function decodeSequencerBatch( } batches.push(decodedBatch) } - console.log(batches[0]) - } else { - const methodName = reader.readBytes(4).toString('hex') - console.log('MethodName:', methodName) - - if (kind === 'Metis' || kind === 'Metis 2.0') { - const chainId = reader.readBytes(32).toString('hex') - console.log('ChainId:', chainId) - } - const shouldStartAtElement = reader.readU40BE() - const totalElementsToAppend = reader.readU24BE() - const contextCount = reader.readU24BE() - - console.log('Should start at Element:', shouldStartAtElement) - console.log('Total Elements to Append:', totalElementsToAppend) - console.log('contextCount:', contextCount) - - const contexts = [] - for (let i = 0; i < contextCount; i++) { - const sequencerTxCount = reader.readU24BE() - const queueTxCount = reader.readU24BE() - const timestamp = reader.readU40BE() - const blockNumber = reader.readU40BE() - contexts.push({ - sequencerTxCount, - queueTxCount, - timestamp, - blockNumber, - }) - console.log(sequencerTxCount, queueTxCount, timestamp, blockNumber) - } + console.log('First batch', batches[0]) + } +} - if (contexts[0].blockNumber === 0 && kind === 'Optimism OVM 2.0') { - console.log( - 'Block number = 0 ? Transactions are compressed, nice.... Decompressing....', - ) - contexts.slice(1) // remove dummy context that indicates compressed transaction data - const bytes = reader.readBytes(reader.left()) - const inflated = zlib.inflateSync(bytes) - reader = new BufferReader(inflated) - } +export async function decodeSequencerBatch( + kind: string, + data: string, + fourBytesApi: FourBytesApi, +): Promise { + console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') + let reader = new BufferReader(Buffer.from(data.slice(2), 'hex')) - const transactions = [] - for (const context of contexts) { - console.log( - 'Block:', - context.blockNumber, - 'Timestamp:', - context.timestamp, - ) - for (let i = 0; i < context.sequencerTxCount; i++) { - const size = reader.readU24BE() - const raw = reader.readBytes(size).toString('hex') - const parsed = ethers.utils.parseTransaction(add0x(raw)) - const methodHash = parsed.data.slice(0, 10) - const methodSignature = await fourBytesApi.getMethodSignature( - methodHash, - ) - transactions.push(add0x(raw)) - console.log(' ', trimLong(add0x(raw)), methodHash, methodSignature) - } + const methodName = reader.readBytes(4).toString('hex') + console.log('MethodName:', methodName) + + if (kind === 'Metis' || kind === 'Metis 2.0') { + const chainId = reader.readBytes(32).toString('hex') + console.log('ChainId:', chainId) + } + const shouldStartAtElement = reader.readU40BE() + const totalElementsToAppend = reader.readU24BE() + const contextCount = reader.readU24BE() + + console.log('Should start at Element:', shouldStartAtElement) + console.log('Total Elements to Append:', totalElementsToAppend) + console.log('contextCount:', contextCount) + + const contexts = [] + for (let i = 0; i < contextCount; i++) { + const sequencerTxCount = reader.readU24BE() + const queueTxCount = reader.readU24BE() + const timestamp = reader.readU40BE() + const blockNumber = reader.readU40BE() + contexts.push({ + sequencerTxCount, + queueTxCount, + timestamp, + blockNumber, + }) + console.log(sequencerTxCount, queueTxCount, timestamp, blockNumber) + } + + if (contexts[0].blockNumber === 0 && kind === 'Optimism OVM 2.0') { + console.log( + 'Block number = 0 ? Transactions are compressed, nice.... Decompressing....', + ) + contexts.slice(1) // remove dummy context that indicates compressed transaction data + const bytes = reader.readBytes(reader.left()) + const inflated = zlib.inflateSync(bytes) + reader = new BufferReader(inflated) + } + + const transactions = [] + for (const context of contexts) { + console.log('Block:', context.blockNumber, 'Timestamp:', context.timestamp) + for (let i = 0; i < context.sequencerTxCount; i++) { + const size = reader.readU24BE() + const raw = reader.readBytes(size).toString('hex') + const parsed = ethers.utils.parseTransaction(add0x(raw)) + const methodHash = parsed.data.slice(0, 10) + const methodSignature = await fourBytesApi.getMethodSignature(methodHash) + transactions.push(add0x(raw)) + console.log(' ', trimLong(add0x(raw)), methodHash, methodSignature) } console.log('Decoded', transactions.length, 'transactions') diff --git a/packages/optimism-decoder/src/run.ts b/packages/optimism-decoder/src/run.ts index 88ff7f09..2196139a 100644 --- a/packages/optimism-decoder/src/run.ts +++ b/packages/optimism-decoder/src/run.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv' import { ethers } from 'ethers' import { analyzeTransaction } from './analyze' -import { decodeSequencerBatch } from './decode' +import { decodeSequencerBatch, decodeOpStackSequencerBatch } from './decode' import { FourBytesApi } from './FourBytesApi' function getEnv(key: string) { @@ -39,6 +39,8 @@ export async function run() { const provider = new ethers.providers.JsonRpcProvider(rpcUrl) const fourBytesApi = new FourBytesApi() - const { data, project } = await analyzeTransaction(provider, txHash) - await decodeSequencerBatch(project, data, fourBytesApi) + const { data, project, kind } = await analyzeTransaction(provider, txHash) + if (kind === 'OpStack') + await decodeOpStackSequencerBatch(project, data, fourBytesApi) + else await decodeSequencerBatch(project, data, fourBytesApi) } From 1a4a63916efceb6dad1aef6a4b84411015124e13 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 17:13:03 +0100 Subject: [PATCH 05/17] full tx decoding for OPStack --- packages/optimism-decoder/src/decode.ts | 40 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index c81c494e..4ccd77df 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -9,7 +9,6 @@ import zlib from 'zlib' import { FourBytesApi } from './FourBytesApi' import { add0x, trimLong } from './utils' -import { decode } from 'punycode' interface BatchContext { sequencerTxCount: number @@ -73,21 +72,46 @@ export async function decodeOpStackSequencerBatch( //console.log(decoded) const batches = [] + let numEmptyBatches = 0 console.log('Decoding', decoded.length, 'batches') - for (const batch of decoded) { - const batchHexWithout00 = batch.slice(4) + for (const [index, batch] of decoded.entries()) { + const batchHexWithout00 = batch.slice(4) // remove '0x00' from the beginning of a batch. 00 signifies batch version number const decodedBatch = ethers.utils.RLP.decode(add0x(batchHexWithout00)) + // decoded batch is [parent_hash, epoch_number, epoch_hash, timestamp, transaction_list] if (decodedBatch[decodedBatch.length - 1].length !== 0) { + // transaction list is not empty + //console.log(batch) + console.log() + console.log('Batch #', index) + const txs = decodedBatch[decodedBatch.length - 1][0] //console.log('txs:', txs) - const transactions = ethers.utils.RLP.decode(add0x(txs.slice(4))) - //console.log('transactions:', transactions) - decodedBatch[decodedBatch.length - 1] = transactions - } + const transaction = ethers.utils.RLP.decode(add0x(txs.slice(4))) //rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) + console.log('RLP Decoded transaction:') + console.log(' ChainId:', parseInt(transaction[0], 16)) + console.log(' SenderNonce:', parseInt(transaction[1], 16)) + console.log(' max_priority_fee_per_gas:', parseInt(transaction[2], 16)) + console.log(' max_fee_per_gas:', parseInt(transaction[3], 16)) + console.log(' gas_limit:', parseInt(transaction[4], 16)) + console.log(' To:', transaction[5]) + console.log(' Value:', transaction[6]) + console.log(' Data:', transaction[7]) + console.log(' AccessList:', transaction[8]) + console.log(' V', transaction[9]) + console.log(' R', transaction[10]) + console.log(' S', transaction[11]) + decodedBatch[decodedBatch.length - 1] = transaction + } else numEmptyBatches++ batches.push(decodedBatch) } - console.log('First batch', batches[0]) + console.log('Num of empty batches', numEmptyBatches) + console.log('First batch:') + console.log(' Parent_hash', batches[0][0]) + console.log(' Epoch_number', batches[0][1]) + console.log(' Epoch_hash', batches[0][2]) + console.log(' Timestamp', batches[0][3]) + console.log(' Tx_list', batches[0][4]) } } From 2689d36e8aa4826a5387c5f16eb1cce8b5693e2c Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 19:28:27 +0100 Subject: [PATCH 06/17] parse OPStack txs --- packages/optimism-decoder/src/analyze.ts | 4 +- packages/optimism-decoder/src/decode.ts | 133 +++++++++-------------- 2 files changed, 56 insertions(+), 81 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 33534e82..811a7781 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -7,10 +7,12 @@ const ctcMapping: Record = { '0x6A1DB7d799FBA381F2a518cA859ED30cB8E1d41a': 'Metis 2.0', '0xfBd2541e316948B259264c02f370eD088E04c3Db': 'Boba Network', '0x5f7f7f6DB967F0ef10BdA0678964DBA185d16c50': 'Lyra', + '0xFf00000000000000000000000000000000008453': 'Base', } const typeMapping: Record = { - 'Lyra': 'OpStack', + Lyra: 'OpStack', + Base: 'OpStack', 'Boba Network': 'OVM 2.0', 'Optimism OVM 1.0': 'OVM 1.0', } diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 4ccd77df..333eeea2 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -32,87 +32,60 @@ export async function decodeOpStackSequencerBatch( console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') let reader = new BufferReader(Buffer.from(data.slice(2), 'hex')) - if (kind === 'Lyra') { - const version = reader.readBytes(1).toString('hex') - console.log('Version:', version) - const channelId = reader.readBytes(16).toString('hex') - console.log('ChannelId:', channelId) - const frame_number = reader.readU16BE() - console.log('Frame Number:', frame_number) - const frame_data_length = reader.readU32BE() - console.log('Frame Data Length:', frame_data_length) - // console.log(reader.left()) - const bytes = reader.readBytes(frame_data_length) - const is_last = reader.readBytes(1).toString('hex') - assert(is_last === '01' || is_last === '00') - console.log('Is Last:', is_last === '01') - const inflated = zlib.inflateSync(bytes) - - // ----- reading decompressed data ----- - - reader = new BufferReader(inflated) - const decompressedBytes = reader.readBytes(reader.left()) - // console.log(add0x(decompressedBytes.toString('hex'))) - - const totalLength = decompressedBytes.toString('hex').length / 2 // we do /2 because we are counting bytes - const lengthBytes = ethers.utils.hexlify(totalLength).slice(2) - console.log('Length Bytes:', lengthBytes) - const lengthBytesLength = lengthBytes.length / 2 - console.log('Length Bytes Length:', lengthBytesLength) - const lengthByte = 0xf7 + lengthBytesLength - console.log('Length Byte:', lengthByte) - const lengthByteHex = ethers.utils.hexlify(lengthByte) - console.log('Length Byte Hex:', lengthByteHex) - const concatenatedWithLength = - lengthByteHex + - lengthBytes + - (decompressedBytes.toString('hex') as string) - //console.log(concatenatedWithLength) - const decoded = ethers.utils.RLP.decode(concatenatedWithLength) - //console.log(decoded) - - const batches = [] - let numEmptyBatches = 0 - console.log('Decoding', decoded.length, 'batches') - for (const [index, batch] of decoded.entries()) { - const batchHexWithout00 = batch.slice(4) // remove '0x00' from the beginning of a batch. 00 signifies batch version number - const decodedBatch = ethers.utils.RLP.decode(add0x(batchHexWithout00)) - // decoded batch is [parent_hash, epoch_number, epoch_hash, timestamp, transaction_list] - - if (decodedBatch[decodedBatch.length - 1].length !== 0) { - // transaction list is not empty - //console.log(batch) - console.log() - console.log('Batch #', index) - - const txs = decodedBatch[decodedBatch.length - 1][0] - //console.log('txs:', txs) - const transaction = ethers.utils.RLP.decode(add0x(txs.slice(4))) //rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) - console.log('RLP Decoded transaction:') - console.log(' ChainId:', parseInt(transaction[0], 16)) - console.log(' SenderNonce:', parseInt(transaction[1], 16)) - console.log(' max_priority_fee_per_gas:', parseInt(transaction[2], 16)) - console.log(' max_fee_per_gas:', parseInt(transaction[3], 16)) - console.log(' gas_limit:', parseInt(transaction[4], 16)) - console.log(' To:', transaction[5]) - console.log(' Value:', transaction[6]) - console.log(' Data:', transaction[7]) - console.log(' AccessList:', transaction[8]) - console.log(' V', transaction[9]) - console.log(' R', transaction[10]) - console.log(' S', transaction[11]) - decodedBatch[decodedBatch.length - 1] = transaction - } else numEmptyBatches++ - batches.push(decodedBatch) - } - console.log('Num of empty batches', numEmptyBatches) - console.log('First batch:') - console.log(' Parent_hash', batches[0][0]) - console.log(' Epoch_number', batches[0][1]) - console.log(' Epoch_hash', batches[0][2]) - console.log(' Timestamp', batches[0][3]) - console.log(' Tx_list', batches[0][4]) + const version = reader.readBytes(1).toString('hex') + console.log('Version:', version) + const channelId = reader.readBytes(16).toString('hex') + console.log('ChannelId:', channelId) + const frame_number = reader.readU16BE() + console.log('Frame Number:', frame_number) + const frame_data_length = reader.readU32BE() + console.log('Frame Data Length:', frame_data_length) + // console.log(reader.left()) + const bytes = reader.readBytes(frame_data_length) + const is_last = reader.readBytes(1).toString('hex') + assert(is_last === '01' || is_last === '00') + console.log('Is Last:', is_last === '01') + const inflated = zlib.inflateSync(bytes) + + // ----- reading decompressed data ----- + + reader = new BufferReader(inflated) + const decompressedBytes = reader.readBytes(reader.left()) + const totalLength = decompressedBytes.toString('hex').length / 2 // we do /2 because we are counting bytes + const lengthBytes = ethers.utils.hexlify(totalLength).slice(2) + const lengthBytesLength = lengthBytes.length / 2 + const lengthByte = 0xf7 + lengthBytesLength + const lengthByteHex = ethers.utils.hexlify(lengthByte) + const concatenatedWithLength = + lengthByteHex + lengthBytes + (decompressedBytes.toString('hex') as string) + const decoded = ethers.utils.RLP.decode(concatenatedWithLength) + + let numEmptyBatches = 0 + console.log('Decoding', decoded.length, 'batches') + + for (const [index, batch] of decoded.entries()) { + // batch: batch_version ++ rlp (parent_hash, epoch_number, epoch_hash, timestamp, transaction_list) + const batchVersion = batch.slice(2, 4) + const decodedBatch = ethers.utils.RLP.decode(add0x(batch.slice(4))) + const numTxs = decodedBatch[decodedBatch.length - 1].length + if (numTxs !== 0) { + // transaction list is not empty + console.log() + console.log('Batch #', index, 'with', numTxs, 'transactions') + console.log(decodedBatch) + + for (const tx of decodedBatch[decodedBatch.length - 1]) { + console.log('tx:', tx) + const parsed = ethers.utils.parseTransaction(tx) + const methodHash = parsed.data.slice(0, 10) + const methodSignature = await fourBytesApi.getMethodSignature( + methodHash, + ) + console.log(' ', trimLong(tx), methodHash, methodSignature) + } + } else numEmptyBatches++ } + console.log('Num of empty batches', numEmptyBatches) } export async function decodeSequencerBatch( From d79f81f350064cc2e779ae9aaa6bc09b6f3465a5 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 19:34:22 +0100 Subject: [PATCH 07/17] clean output --- packages/optimism-decoder/src/decode.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 333eeea2..388d6ff1 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -65,17 +65,19 @@ export async function decodeOpStackSequencerBatch( for (const [index, batch] of decoded.entries()) { // batch: batch_version ++ rlp (parent_hash, epoch_number, epoch_hash, timestamp, transaction_list) - const batchVersion = batch.slice(2, 4) const decodedBatch = ethers.utils.RLP.decode(add0x(batch.slice(4))) const numTxs = decodedBatch[decodedBatch.length - 1].length if (numTxs !== 0) { // transaction list is not empty console.log() console.log('Batch #', index, 'with', numTxs, 'transactions') - console.log(decodedBatch) + console.log('ParentHash', decodedBatch[0]) + console.log('EpochNumber', parseInt(decodedBatch[1], 16)) + console.log('TimeStamp', parseInt(decodedBatch[2], 16)) + console.log('EpochHash', decodedBatch[3]) for (const tx of decodedBatch[decodedBatch.length - 1]) { - console.log('tx:', tx) + //console.log('tx:', tx) const parsed = ethers.utils.parseTransaction(tx) const methodHash = parsed.data.slice(0, 10) const methodSignature = await fourBytesApi.getMethodSignature( From c999f13a963db70d7c103ae72c9aa228db97f7c9 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 19:36:14 +0100 Subject: [PATCH 08/17] small fix --- packages/optimism-decoder/src/decode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 388d6ff1..8676a1aa 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -73,8 +73,8 @@ export async function decodeOpStackSequencerBatch( console.log('Batch #', index, 'with', numTxs, 'transactions') console.log('ParentHash', decodedBatch[0]) console.log('EpochNumber', parseInt(decodedBatch[1], 16)) - console.log('TimeStamp', parseInt(decodedBatch[2], 16)) - console.log('EpochHash', decodedBatch[3]) + console.log('EpochHash', decodedBatch[2]) + console.log('Timestamp', parseInt(decodedBatch[3], 16)) for (const tx of decodedBatch[decodedBatch.length - 1]) { //console.log('tx:', tx) From 53b4c0310a80a34f3d00bab88a28fde95882b6b5 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 21:33:06 +0100 Subject: [PATCH 09/17] add timestamps --- packages/optimism-decoder/src/analyze.ts | 2 ++ packages/optimism-decoder/src/decode.ts | 6 +++++- packages/optimism-decoder/src/run.ts | 10 +++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 811a7781..5eaac5c3 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -22,6 +22,7 @@ export async function analyzeTransaction( txHash: string, ) { const tx = await provider.getTransaction(txHash) + const block = await provider.getBlock(tx.blockNumber) const project = ctcMapping[tx.to ?? ''] ?? 'Unknown' const kind = typeMapping[project ?? ''] ?? 'Unknown' console.log( @@ -35,6 +36,7 @@ export async function analyzeTransaction( return { data: tx.data, + timestamp: block.timestamp, project, kind, } diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 8676a1aa..f839287d 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -63,6 +63,7 @@ export async function decodeOpStackSequencerBatch( let numEmptyBatches = 0 console.log('Decoding', decoded.length, 'batches') + const timestamps = [] for (const [index, batch] of decoded.entries()) { // batch: batch_version ++ rlp (parent_hash, epoch_number, epoch_hash, timestamp, transaction_list) const decodedBatch = ethers.utils.RLP.decode(add0x(batch.slice(4))) @@ -74,7 +75,9 @@ export async function decodeOpStackSequencerBatch( console.log('ParentHash', decodedBatch[0]) console.log('EpochNumber', parseInt(decodedBatch[1], 16)) console.log('EpochHash', decodedBatch[2]) - console.log('Timestamp', parseInt(decodedBatch[3], 16)) + const timestamp = parseInt(decodedBatch[3], 16) + console.log('Timestamp', timestamp) + timestamps.push(timestamp) for (const tx of decodedBatch[decodedBatch.length - 1]) { //console.log('tx:', tx) @@ -88,6 +91,7 @@ export async function decodeOpStackSequencerBatch( } else numEmptyBatches++ } console.log('Num of empty batches', numEmptyBatches) + console.log(timestamps) } export async function decodeSequencerBatch( diff --git a/packages/optimism-decoder/src/run.ts b/packages/optimism-decoder/src/run.ts index 2196139a..03852ee3 100644 --- a/packages/optimism-decoder/src/run.ts +++ b/packages/optimism-decoder/src/run.ts @@ -39,8 +39,12 @@ export async function run() { const provider = new ethers.providers.JsonRpcProvider(rpcUrl) const fourBytesApi = new FourBytesApi() - const { data, project, kind } = await analyzeTransaction(provider, txHash) - if (kind === 'OpStack') + const { data, timestamp, project, kind } = await analyzeTransaction( + provider, + txHash, + ) + if (kind === 'OpStack') { await decodeOpStackSequencerBatch(project, data, fourBytesApi) - else await decodeSequencerBatch(project, data, fourBytesApi) + console.log('Batch submission timestamp:', timestamp) + } else await decodeSequencerBatch(project, data, fourBytesApi) } From 4e55fe4a229742b7c9a3529b4c065750b7ba7d34 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 21:49:15 +0100 Subject: [PATCH 10/17] added zora --- packages/optimism-decoder/src/analyze.ts | 2 ++ packages/optimism-decoder/src/decode.ts | 9 ++++++++- packages/optimism-decoder/src/run.ts | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 5eaac5c3..e1f7732b 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -8,11 +8,13 @@ const ctcMapping: Record = { '0xfBd2541e316948B259264c02f370eD088E04c3Db': 'Boba Network', '0x5f7f7f6DB967F0ef10BdA0678964DBA185d16c50': 'Lyra', '0xFf00000000000000000000000000000000008453': 'Base', + '0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf': 'Zora', } const typeMapping: Record = { Lyra: 'OpStack', Base: 'OpStack', + Zora: 'OpStack', 'Boba Network': 'OVM 2.0', 'Optimism OVM 1.0': 'OVM 1.0', } diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index f839287d..9b18878e 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -27,6 +27,7 @@ interface AppendSequencerBatchParams { export async function decodeOpStackSequencerBatch( kind: string, data: string, + submissionTimestamp: number, fourBytesApi: FourBytesApi, ) { console.log('Decoding', kind, 'L1 Sequencer transaction batch ...') @@ -91,7 +92,13 @@ export async function decodeOpStackSequencerBatch( } else numEmptyBatches++ } console.log('Num of empty batches', numEmptyBatches) - console.log(timestamps) + console.log( + 'Finality delay between', + submissionTimestamp - Math.min(...timestamps), + 'and', + submissionTimestamp - Math.max(...timestamps), + 'seconds', + ) } export async function decodeSequencerBatch( diff --git a/packages/optimism-decoder/src/run.ts b/packages/optimism-decoder/src/run.ts index 03852ee3..ea645428 100644 --- a/packages/optimism-decoder/src/run.ts +++ b/packages/optimism-decoder/src/run.ts @@ -44,7 +44,7 @@ export async function run() { txHash, ) if (kind === 'OpStack') { - await decodeOpStackSequencerBatch(project, data, fourBytesApi) + await decodeOpStackSequencerBatch(project, data, timestamp, fourBytesApi) console.log('Batch submission timestamp:', timestamp) } else await decodeSequencerBatch(project, data, fourBytesApi) } From 7f35b06fd497b79bc6ff37e88093d9714756bf30 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sun, 7 Jan 2024 21:53:21 +0100 Subject: [PATCH 11/17] add PGN --- packages/optimism-decoder/src/analyze.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index e1f7732b..04e3d3d2 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -9,12 +9,14 @@ const ctcMapping: Record = { '0x5f7f7f6DB967F0ef10BdA0678964DBA185d16c50': 'Lyra', '0xFf00000000000000000000000000000000008453': 'Base', '0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf': 'Zora', + '0xC1B90E1e459aBBDcEc4DCF90dA45ba077d83BFc5': 'PGN', } const typeMapping: Record = { Lyra: 'OpStack', Base: 'OpStack', Zora: 'OpStack', + PGN: 'OpStack', 'Boba Network': 'OVM 2.0', 'Optimism OVM 1.0': 'OVM 1.0', } From 9db933f6a37a6891cdf58c5d2d46a6e5aa6fd45d Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Mon, 8 Jan 2024 14:00:33 +0100 Subject: [PATCH 12/17] add more chains --- packages/optimism-decoder/src/analyze.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 04e3d3d2..5a8b748c 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -10,6 +10,8 @@ const ctcMapping: Record = { '0xFf00000000000000000000000000000000008453': 'Base', '0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf': 'Zora', '0xC1B90E1e459aBBDcEc4DCF90dA45ba077d83BFc5': 'PGN', + '0xFF00000000000000000000000000000000000010': 'OPMainnet', + '0x253887577420Cb7e7418cD4d50147743c8041b28': 'Aevo' } const typeMapping: Record = { @@ -17,6 +19,8 @@ const typeMapping: Record = { Base: 'OpStack', Zora: 'OpStack', PGN: 'OpStack', + OPMainnet: 'OpStack', + Aevo: 'OpStack', 'Boba Network': 'OVM 2.0', 'Optimism OVM 1.0': 'OVM 1.0', } From dcbb2afeeaf2e35dc193d3b43f52f1736459d4fd Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sat, 13 Jan 2024 10:33:27 +0100 Subject: [PATCH 13/17] handling incomplete channel --- packages/optimism-decoder/src/decode.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 9b18878e..925d0b0f 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -39,6 +39,12 @@ export async function decodeOpStackSequencerBatch( console.log('ChannelId:', channelId) const frame_number = reader.readU16BE() console.log('Frame Number:', frame_number) + if (frame_number !== 0) { + console.log( + "This is not a first frame, I won't be able to decompress this, exiting...", + ) + return + } const frame_data_length = reader.readU32BE() console.log('Frame Data Length:', frame_data_length) // console.log(reader.left()) @@ -46,6 +52,13 @@ export async function decodeOpStackSequencerBatch( const is_last = reader.readBytes(1).toString('hex') assert(is_last === '01' || is_last === '00') console.log('Is Last:', is_last === '01') + if (is_last === '00') { + console.log( + "This is not a last frame, I won't be able to decompress this, exiting...", + ) + return + } + const inflated = zlib.inflateSync(bytes) // ----- reading decompressed data ----- From e016b0c975595391eac150cbb2fe4926ce4f109d Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Fri, 19 Jan 2024 21:54:34 +0100 Subject: [PATCH 14/17] add arbitrum decoder --- packages/optimism-decoder/src/analyze.ts | 4 +++- packages/optimism-decoder/src/decode.ts | 28 ++++++++++++++++++++++++ packages/optimism-decoder/src/run.ts | 6 +++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/optimism-decoder/src/analyze.ts b/packages/optimism-decoder/src/analyze.ts index 5a8b748c..7ef601a2 100644 --- a/packages/optimism-decoder/src/analyze.ts +++ b/packages/optimism-decoder/src/analyze.ts @@ -11,10 +11,12 @@ const ctcMapping: Record = { '0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf': 'Zora', '0xC1B90E1e459aBBDcEc4DCF90dA45ba077d83BFc5': 'PGN', '0xFF00000000000000000000000000000000000010': 'OPMainnet', - '0x253887577420Cb7e7418cD4d50147743c8041b28': 'Aevo' + '0x253887577420Cb7e7418cD4d50147743c8041b28': 'Aevo', + '0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6': 'Arbitrum', } const typeMapping: Record = { + Arbitrum: 'Arbitrum', Lyra: 'OpStack', Base: 'OpStack', Zora: 'OpStack', diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 925d0b0f..dfe57fc2 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -24,6 +24,34 @@ interface AppendSequencerBatchParams { transactions: string[] // total_size_bytes[], total_size_bytes[] } +export async function decodeArbitrumBatch( + kind: string, + data: string, + fourBytesApi: FourBytesApi, +) { + console.log('Decoding Arbitrum...') + const abi = [ + 'function addSequencerL2BatchFromOrigin(uint256 sequenceNumber,bytes data,uint256 afterDelayedMessagesRead,address gasRefunder,uint256 prevMessageCount,uint256 newMessageCount)', + ] + const iface = new ethers.utils.Interface(abi) + const decodedArgs = iface.decodeFunctionData(data.slice(0, 10), data) + console.log(decodedArgs.data.slice(2, 4)) // removing 0x, next byte is type of compressed data + let brotliCompressedData = Buffer.from(data.slice(4), 'hex') + try { + let decompressedData = zlib.brotliDecompressSync(brotliCompressedData, { + //TODO: No idea what are the correct params + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC, + [zlib.constants.BROTLI_PARAM_QUALITY]: + zlib.constants.BROTLI_MAX_QUALITY, + }, + }) + console.log('Decompressed data:', decompressedData.toString()) + } catch (err) { + console.error('An error occurred:', err) + } +} + export async function decodeOpStackSequencerBatch( kind: string, data: string, diff --git a/packages/optimism-decoder/src/run.ts b/packages/optimism-decoder/src/run.ts index ea645428..10813e0b 100644 --- a/packages/optimism-decoder/src/run.ts +++ b/packages/optimism-decoder/src/run.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv' import { ethers } from 'ethers' import { analyzeTransaction } from './analyze' -import { decodeSequencerBatch, decodeOpStackSequencerBatch } from './decode' +import { decodeSequencerBatch, decodeOpStackSequencerBatch, decodeArbitrumBatch } from './decode' import { FourBytesApi } from './FourBytesApi' function getEnv(key: string) { @@ -46,5 +46,7 @@ export async function run() { if (kind === 'OpStack') { await decodeOpStackSequencerBatch(project, data, timestamp, fourBytesApi) console.log('Batch submission timestamp:', timestamp) - } else await decodeSequencerBatch(project, data, fourBytesApi) + } else if (kind === 'Arbitrum') + await decodeArbitrumBatch(project, data, fourBytesApi) + else await decodeSequencerBatch(project, data, fourBytesApi) } From f09c43357de0454dc4c1ca09908720edfb9c6c8b Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Sat, 20 Jan 2024 09:30:53 +0100 Subject: [PATCH 15/17] fix small bug --- packages/optimism-decoder/src/decode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index dfe57fc2..9644cb14 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -36,7 +36,7 @@ export async function decodeArbitrumBatch( const iface = new ethers.utils.Interface(abi) const decodedArgs = iface.decodeFunctionData(data.slice(0, 10), data) console.log(decodedArgs.data.slice(2, 4)) // removing 0x, next byte is type of compressed data - let brotliCompressedData = Buffer.from(data.slice(4), 'hex') + let brotliCompressedData = Buffer.from(decodedArgs.data.slice(4), 'hex') try { let decompressedData = zlib.brotliDecompressSync(brotliCompressedData, { //TODO: No idea what are the correct params From 0f8d955f09d2da26370f4497f13e2fb6d13495c3 Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Mon, 22 Jan 2024 10:50:09 +0100 Subject: [PATCH 16/17] decode fully Arbitrum batch submissions --- packages/optimism-decoder/src/decode.ts | 177 +++++++++++++++++++++++- packages/optimism-decoder/src/run.ts | 8 +- 2 files changed, 180 insertions(+), 5 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 9644cb14..5f18fb52 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -9,6 +9,9 @@ import zlib from 'zlib' import { FourBytesApi } from './FourBytesApi' import { add0x, trimLong } from './utils' +import { decode } from 'punycode' +import { parse } from 'path' +import { mnemonicToEntropy } from 'ethers/lib/utils' interface BatchContext { sequencerTxCount: number @@ -23,12 +26,134 @@ interface AppendSequencerBatchParams { contexts: BatchContext[] // total_elements[fixed_size[]] transactions: string[] // total_size_bytes[], total_size_bytes[] } +/* -export async function decodeArbitrumBatch( +//4d73adb72bc3dd368966edd0f0b2148401a178e2 + +86 038465ab8606 +86 04840122a208 +b9 0b77 0003000000000000027404f9027083597a5d8407270e00835ca96d94a0cc33dd6f4819d473226257792afe230ec3c67f80b902046c459a28 +000000000000000000000000 +4d73adb72bc3dd368966edd0f0b2148401a178e2 +00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000065abda65 +*/ + +/* L1MessageType_L2Message = 3 + L1MessageType_EndOfBlock = 6 + L1MessageType_L2FundedByL1 = 7 + L1MessageType_RollupEvent = 8 + L1MessageType_SubmitRetryable = 9 + L1MessageType_BatchForGasEstimation = 10 // probably won't use this in practice + L1MessageType_Initialize = 11 + L1MessageType_EthDeposit = 12 + L1MessageType_BatchPostingReport = 13 + L1MessageType_Invalid = 0xFF + /* + +/* const BatchSegmentKindL2Message uint8 = 0 +const BatchSegmentKindL2MessageBrotli uint8 = 1 +const BatchSegmentKindDelayedMessages uint8 = 2 +const BatchSegmentKindAdvanceTimestamp uint8 = 3 +const BatchSegmentKindAdvanceL1BlockNumber uint8 = 4 +*/ + +/* L2MessageKind_UnsignedUserTx = 0 + L2MessageKind_ContractTx = 1 + L2MessageKind_NonmutatingCall = 2 + L2MessageKind_Batch = 3 + L2MessageKind_SignedTx = 4 + // 5 is reserved + L2MessageKind_Heartbeat = 6 // deprecated + L2MessageKind_SignedCompressedTx = 7 + // 8 is reserved for BLS signed batch +) */ + +export function decodeArbitrumL2Message( + tx: string, + fourBytesApi: FourBytesApi, +) { + const type = tx.slice(0, 2) + //console.log(' Type:', type) + const rawTx = add0x(tx.slice(2)) + const parsed = ethers.utils.parseTransaction(rawTx) + const methodHash = parsed.data.slice(0, 10) + // const methodSignature = await fourBytesApi.getMethodSignature(methodHash) + const methodSignature = '???' + //console.log( + // ' ', + // trimLong(tx), + // methodHash, + // methodSignature, + // parsed.from, + // parsed.to, + // ) + //console.log(parsed.from, parsed.to) +} + +export function decodeArbitrumL2MessageBatch( + l2Message: string, + fourBytesApi: FourBytesApi, +) { + //console.log('decoding L2Message:') + //console.log(l2Message) + //console.log() + let totalRead = 0 + for (let i = 0; ; i++) { + const length = parseInt(l2Message.slice(totalRead, totalRead + 16), 16) * 2 + //console.log(' TxChunkLength:', i, +length) + const tx = l2Message.slice(totalRead + 16, totalRead + 16 + length) + //console.log(tx, tx.length) + //decodeArbitrumL2Message(tx, fourBytesApi) + totalRead += length + 16 + //console.log('TotalRead: ', totalRead) + if (totalRead >= l2Message.length) break + } +} + +export function decodeArbitrumSegment( + segment: string, + fourBytesApi: FourBytesApi, +): string { + const segmentContentType = segment.slice(0, 2) + let timestamp = '0x00' + //console.log('SegmentContentType: ', segmentContentType) + switch (segmentContentType) { + case '00': // Batch of singed transactions + if (segment.slice(2, 4) === '03') { + decodeArbitrumL2MessageBatch(segment.slice(4), fourBytesApi) + } else { + const tx = segment.slice(4) + decodeArbitrumL2Message(add0x(tx), fourBytesApi) + } + break + case '03': // AdvanceTimestamp + 4 bytes + timestamp = ethers.utils.RLP.decode(add0x(segment.slice(2))) + //console.log(' AdvanceTimestamp:', timestamp, parseInt(timestamp, 16)) + break + case '04': // AdvanceL1BlockNumber + 4 bytes + const l1block = ethers.utils.RLP.decode(add0x(segment.slice(2))) + //console.log(' AdvanceL1BlockNumber:', l1block, parseInt(l1block, 16)) + break + default: + console.log( + 'Unknown segment type', + segmentContentType, + segment.slice(4), + parseInt(segment.slice(4), 16), + ) + } + return timestamp +} + +export function decodeArbitrumBatch( kind: string, data: string, + submissionTimestamp: number, fourBytesApi: FourBytesApi, ) { + let minTimestamp, maxTimestamp, minT, maxT + let firstTimestamp = true + console.log('Decoding Arbitrum...') const abi = [ 'function addSequencerL2BatchFromOrigin(uint256 sequenceNumber,bytes data,uint256 afterDelayedMessagesRead,address gasRefunder,uint256 prevMessageCount,uint256 newMessageCount)', @@ -39,14 +164,60 @@ export async function decodeArbitrumBatch( let brotliCompressedData = Buffer.from(decodedArgs.data.slice(4), 'hex') try { let decompressedData = zlib.brotliDecompressSync(brotliCompressedData, { - //TODO: No idea what are the correct params + //TODO: No idea what are the correct params. params: { [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC, [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, }, }) - console.log('Decompressed data:', decompressedData.toString()) + //console.log('Decompressed data:', decompressedData) + let reader = new BufferReader(decompressedData) + //reader contains uncompressed list of segments. Each segment is RLP decoded. + for (let i = 0; i < 152; i++) { + //152 + // read segment length + let segmentLength = 0 + const lengthType = parseInt(reader.readBytes(1).toString('hex'), 16) + //console.log(lengthType) + if (lengthType >= 128 && lengthType <= 183) { + segmentLength = lengthType - 128 + } else if (lengthType >= 184 && lengthType <= 191) { + // 0xb7 - 0xbf + const lenghtOflength = lengthType - 183 + segmentLength = parseInt( + reader.readBytes(lenghtOflength).toString('hex'), + 16, + ) + } else { + console.log(reader.readBytes(1).toString('hex')) + break + } + //console.log('Segment', i, 'of length', segmentLength) + const value = reader.readBytes(segmentLength).toString('hex') + //console.log(value) + + const timestamp = decodeArbitrumSegment(value, fourBytesApi) + if (firstTimestamp) { + minTimestamp = parseInt(timestamp, 16) + maxTimestamp = parseInt(timestamp, 16) + firstTimestamp = false + } else { + maxTimestamp += parseInt(timestamp, 16) + } + minT = minTimestamp + maxT = maxTimestamp + } + console.log('Submission timestamp:', submissionTimestamp) + console.log('Min L2 timestamp in submission:', minTimestamp) + console.log('Max L2 timestamp in submission:', maxTimestamp) + console.log( + 'Finality delay between', + submissionTimestamp - minTimestamp, + 'and', + submissionTimestamp - maxTimestamp, + 'seconds', + ) } catch (err) { console.error('An error occurred:', err) } diff --git a/packages/optimism-decoder/src/run.ts b/packages/optimism-decoder/src/run.ts index 10813e0b..35146bf2 100644 --- a/packages/optimism-decoder/src/run.ts +++ b/packages/optimism-decoder/src/run.ts @@ -2,7 +2,11 @@ import dotenv from 'dotenv' import { ethers } from 'ethers' import { analyzeTransaction } from './analyze' -import { decodeSequencerBatch, decodeOpStackSequencerBatch, decodeArbitrumBatch } from './decode' +import { + decodeSequencerBatch, + decodeOpStackSequencerBatch, + decodeArbitrumBatch, +} from './decode' import { FourBytesApi } from './FourBytesApi' function getEnv(key: string) { @@ -47,6 +51,6 @@ export async function run() { await decodeOpStackSequencerBatch(project, data, timestamp, fourBytesApi) console.log('Batch submission timestamp:', timestamp) } else if (kind === 'Arbitrum') - await decodeArbitrumBatch(project, data, fourBytesApi) + await decodeArbitrumBatch(project, data, timestamp, fourBytesApi) else await decodeSequencerBatch(project, data, fourBytesApi) } From e9ce9dc396c16712ec3ecd226900d50099560afd Mon Sep 17 00:00:00 2001 From: Bartek Kiepuszewski Date: Tue, 23 Jan 2024 09:12:44 +0100 Subject: [PATCH 17/17] fixed arbitrum decoding loop --- packages/optimism-decoder/src/decode.ts | 64 ++++++++++++------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/optimism-decoder/src/decode.ts b/packages/optimism-decoder/src/decode.ts index 5f18fb52..2d045636 100644 --- a/packages/optimism-decoder/src/decode.ts +++ b/packages/optimism-decoder/src/decode.ts @@ -12,6 +12,7 @@ import { add0x, trimLong } from './utils' import { decode } from 'punycode' import { parse } from 'path' import { mnemonicToEntropy } from 'ethers/lib/utils' +import { exit } from 'process' interface BatchContext { sequencerTxCount: number @@ -50,7 +51,10 @@ b9 0b77 0003000000000000027404f9027083597a5d8407270e00835ca96d94a0cc33dd6f4819d4 L1MessageType_Invalid = 0xFF /* -/* const BatchSegmentKindL2Message uint8 = 0 +/* +ARBITRUM SEGMENT TYPES: + +const BatchSegmentKindL2Message uint8 = 0 const BatchSegmentKindL2MessageBrotli uint8 = 1 const BatchSegmentKindDelayedMessages uint8 = 2 const BatchSegmentKindAdvanceTimestamp uint8 = 3 @@ -118,7 +122,7 @@ export function decodeArbitrumSegment( let timestamp = '0x00' //console.log('SegmentContentType: ', segmentContentType) switch (segmentContentType) { - case '00': // Batch of singed transactions + case '00': // Batch of signed transactions if (segment.slice(2, 4) === '03') { decodeArbitrumL2MessageBatch(segment.slice(4), fourBytesApi) } else { @@ -151,7 +155,7 @@ export function decodeArbitrumBatch( submissionTimestamp: number, fourBytesApi: FourBytesApi, ) { - let minTimestamp, maxTimestamp, minT, maxT + let minTimestamp, maxTimestamp let firstTimestamp = true console.log('Decoding Arbitrum...') @@ -173,31 +177,21 @@ export function decodeArbitrumBatch( }) //console.log('Decompressed data:', decompressedData) let reader = new BufferReader(decompressedData) - //reader contains uncompressed list of segments. Each segment is RLP decoded. - for (let i = 0; i < 152; i++) { - //152 - // read segment length - let segmentLength = 0 - const lengthType = parseInt(reader.readBytes(1).toString('hex'), 16) - //console.log(lengthType) - if (lengthType >= 128 && lengthType <= 183) { - segmentLength = lengthType - 128 - } else if (lengthType >= 184 && lengthType <= 191) { - // 0xb7 - 0xbf - const lenghtOflength = lengthType - 183 - segmentLength = parseInt( - reader.readBytes(lenghtOflength).toString('hex'), - 16, - ) - } else { - console.log(reader.readBytes(1).toString('hex')) - break - } - //console.log('Segment', i, 'of length', segmentLength) - const value = reader.readBytes(segmentLength).toString('hex') - //console.log(value) - const timestamp = decodeArbitrumSegment(value, fourBytesApi) + const decompressedBytes = reader.readBytes(reader.left()) + const totalLength = decompressedBytes.toString('hex').length / 2 // we do /2 because we are counting bytes + const lengthBytes = ethers.utils.hexlify(totalLength).slice(2) + const lengthBytesLength = lengthBytes.length / 2 + const lengthByte = 0xf7 + lengthBytesLength + const lengthByteHex = ethers.utils.hexlify(lengthByte) + const concatenatedWithLength = + lengthByteHex + + lengthBytes + + (decompressedBytes.toString('hex') as string) + const decoded = ethers.utils.RLP.decode(concatenatedWithLength) + console.log('Decoded:', decoded.length) + for (const [index, value] of decoded.entries()) { + const timestamp = decodeArbitrumSegment(value.slice(2), fourBytesApi) if (firstTimestamp) { minTimestamp = parseInt(timestamp, 16) maxTimestamp = parseInt(timestamp, 16) @@ -205,18 +199,22 @@ export function decodeArbitrumBatch( } else { maxTimestamp += parseInt(timestamp, 16) } - minT = minTimestamp - maxT = maxTimestamp } console.log('Submission timestamp:', submissionTimestamp) console.log('Min L2 timestamp in submission:', minTimestamp) console.log('Max L2 timestamp in submission:', maxTimestamp) + const minT = submissionTimestamp - minTimestamp + const maxT = submissionTimestamp - maxTimestamp console.log( 'Finality delay between', - submissionTimestamp - minTimestamp, + minT, + 'and', + maxT, + 'seconds (', + parseFloat((minT / 60).toFixed(2)), 'and', - submissionTimestamp - maxTimestamp, - 'seconds', + parseFloat((maxT / 60).toFixed(2)), + 'minutes)', ) } catch (err) { console.error('An error occurred:', err) @@ -260,7 +258,7 @@ export async function decodeOpStackSequencerBatch( const inflated = zlib.inflateSync(bytes) - // ----- reading decompressed data ----- + // ----- reading decompressed data ----- This is RLP list w/out the header, so we need to add header reader = new BufferReader(inflated) const decompressedBytes = reader.readBytes(reader.left())