Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Span batches #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/batches/RawSpanBatch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
import { NestedUint8Array } from 'rlp'
import { InnerBatch } from './batch'

/*
type spanBatchPrefix struct {
relTimestamp uint64 // Relative timestamp of the first block
l1OriginNum uint64 // L1 origin number
parentCheck [20]byte // First 20 bytes of the first block's parent hash
l1OriginCheck [20]byte // First 20 bytes of the last block's L1 origin hash
}

type spanBatchPayload struct {
blockCount uint64 // Number of L2 block in the span
originBits *big.Int // Standard span-batch bitlist of blockCount bits. Each bit indicates if the L1 origin is changed at the L2 block.
blockTxCounts []uint64 // List of transaction counts for each L2 block
txs *spanBatchTxs // Transactions encoded in SpanBatch specs
}
*/

// https://ethereum.stackexchange.com/questions/163066/how-is-rollup-data-verified-with-blobs
// Span batches (post-Delta hardfork)
// https://specs.optimism.io/protocol/delta/span-batches.html#span-batch-format
export class RawSpanBatch {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static decode(data: Uint8Array | NestedUint8Array): InnerBatch {
// TODO: implement
// TODO: implement: prefix ++ payload
// const decoded = rlp.decode(data)
// https://github.com/ethereum-optimism/optimism/blob/375b9766bdf4678253932beae8234cc52f1f46ee/op-node/rollup/derive/span_batch.go#L49
return {} as InnerBatch
}
}
4 changes: 4 additions & 0 deletions src/batches/SingularBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { OpStackTransactionSerialized } from 'viem/chains'
import { parseTransaction } from 'viem/op-stack'
import type { InnerBatch } from './batch'

// https://github.com/ethereum-optimism/optimism/blob/375b9766bdf4678253932beae8234cc52f1f46ee/op-node/rollup/derive/singular_batch.go#L22
// https://specs.optimism.io/protocol/derivation.html#batch-submission-wire-format
// Singular batches (pre-Delta hardfork)
// https://specs.optimism.io/protocol/derivation.html#batch-format
export class SingularBatch {
static decode(data: Uint8Array | NestedUint8Array): InnerBatch {
const decoded = rlp.decode(data)
Expand Down
12 changes: 8 additions & 4 deletions src/batches/batch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import rlp, { NestedUint8Array } from 'rlp'
import zlib from 'zlib'
import stream from 'stream'
import { SingularBatch } from './SingularBatch'
import zlib from 'zlib'
import { RawSpanBatch } from './RawSpanBatch'
import { SingularBatch } from './SingularBatch'

type Transaction = {
type?: string
Expand Down Expand Up @@ -53,11 +53,15 @@ export const parseBatchesData = async (compressedBatches: string): Promise<Batch
return decodedBatches
}

const decompressBatches = async (compressedBatches: string): Promise<Buffer> => {
export const decompressBatches = async (compressedBatches: string): Promise<Buffer> => {
const inputBuffer = Buffer.from(compressedBatches, 'hex')

try {
// Decompress the input buffer
const decompress = zlib.createInflate({ maxOutputLength: MAX_BYTES_PER_CHANNEL })
const decompress = zlib.createInflate({
maxOutputLength: MAX_BYTES_PER_CHANNEL,
finishFlush: zlib.constants.Z_SYNC_FLUSH // required when decompressing span batches, otherwise "Error: unexpected end of file"
})
const decompressStream = stream.Readable.from(inputBuffer)

const chunks: Buffer[] = []
Expand Down
8 changes: 8 additions & 0 deletions src/frames/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@ export const extractFrames = (data: string): FramesWithCompressedData => {
}

const channelId = data.slice(offset, offset + BYTES_16_LENGTH)
console.log('channel:', channelId)

offset += BYTES_16_LENGTH

const frameNumber = Number(`0x${data.slice(offset, offset + BYTES_2_LENGTH)}`)
console.log('frame num:', frameNumber)

offset += BYTES_2_LENGTH

const frameDataLengthInBytes = Number(`0x${data.slice(offset, offset + BYTES_4_LENGTH)}`)
console.log('frame data length:', frameDataLengthInBytes)

offset += BYTES_4_LENGTH
const frameDataLength = frameDataLengthInBytes * BYTE_CHARS

Expand All @@ -51,6 +57,8 @@ export const extractFrames = (data: string): FramesWithCompressedData => {
offset += frameDataLength

const isLast = Number(`0x${data.slice(offset, offset + BYTES_1_LENGTH)}`) !== 0
console.log('is_last:', Number(`0x${data.slice(offset, offset + BYTES_1_LENGTH)}`))

offset += BYTES_1_LENGTH

frames.push({ channelId, frameNumber, data: frameData, isLast })
Expand Down
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BatcherTransaction, extractBatcherTransaction } from './transactions/ba
export const testWithExampleData = async (
filePath: string = 'example-data/calldata.txt'
): Promise<BatcherTransaction> => {
console.log('testing with', filePath)
const examplePath = path.join(path.dirname(__dirname), filePath)
const exampleCallData = fs.readFileSync(examplePath).toString()
return await extractBatcherTransaction(exampleCallData)
Expand All @@ -21,3 +22,18 @@ export const decodeBatcherTransaction = async (txHash: string, providerUrl: stri
export const decodeBatcherTransactionCalldata = async (calldata: string): Promise<BatcherTransaction> => {
return await extractBatcherTransaction(calldata)
}

testWithExampleData()
.then((result) => {
console.log('Batch:')
console.log(result)
// console.log('Frames:')
// console.log(result['frames'])
// console.log('Frame batches:')
// console.log(result['frames'][0]['batches'])
// console.log('Transactions:')
// console.log(result['frames'][0]['batches'][0]['inner']['transactions'])
})
.catch((error) => {
console.error('An error occurred:', error)
})
Loading