diff --git a/db/scripts/4_create_views.sql b/db/scripts/4_create_views.sql index 45048caeb51a7..0f8a04efd9383 100644 --- a/db/scripts/4_create_views.sql +++ b/db/scripts/4_create_views.sql @@ -56,7 +56,7 @@ ORDER BY l1.batch_index ASC CREATE OR REPLACE VIEW batchable_l2_only_tx_states AS -SELECT tx.*, row_number() over (ORDER BY tx.id ASC) -1 AS row_number +SELECT tx.*, row_number() over (ORDER BY tx.block_number ASC, tx.tx_index ASC) -1 AS row_number FROM l2_tx_output tx INNER JOIN canonical_chain_batch cc ON tx.canonical_chain_batch_number = cc.batch_number diff --git a/packages/rollup-core/src/app/data/data-service.ts b/packages/rollup-core/src/app/data/data-service.ts index 620cf574c5619..55f57dcb0bbd7 100644 --- a/packages/rollup-core/src/app/data/data-service.ts +++ b/packages/rollup-core/src/app/data/data-service.ts @@ -422,7 +422,7 @@ export class DefaultDataService implements DataService { const batchTimestamp = parseInt(txRes[0]['block_timestamp'], 10) - const maxTxId = await this.getMaxL2TxOutputIdForCanonicalChainBatch( + const maxBlockNumber = await this.getMaxL2TxOutputBlockNumberForCanonicalChainBatch( batchTimestamp, maxBatchCalldataBytes ) @@ -434,20 +434,19 @@ export class DefaultDataService implements DataService { `UPDATE l2_tx_output tx SET canonical_chain_batch_number = ${batchNumber}, - canonical_chain_batch_index = t.row_number + canonical_chain_batch_index = t.batch_index FROM ( - SELECT id, row_number() over (ORDER BY id) -1 as row_number + SELECT id, row_number() over (ORDER BY block_number ASC, tx_index ASC) -1 as batch_index FROM l2_tx_output WHERE canonical_chain_batch_number IS NULL AND l1_rollup_tx_id IS NULL AND block_timestamp = ${batchTimestamp} - AND id <= ${maxTxId} + AND block_number <= ${maxBlockNumber} ORDER BY block_number ASC, tx_index ASC ) t - WHERE tx.id = t.id - `, + WHERE tx.id = t.id`, txContext ) @@ -460,19 +459,19 @@ export class DefaultDataService implements DataService { } } - public async getMaxL2TxOutputIdForCanonicalChainBatch( + public async getMaxL2TxOutputBlockNumberForCanonicalChainBatch( batchTimestamp: number, maxBatchCalldataBytes: number ): Promise { const res: Row[] = await this.rdb.select( `SELECT - id, + block_number, GREATEST(LENGTH(calldata)-2, 0) / 2 + ${ROLLUP_TX_SIZE_IN_BYTES_MINUS_CALLDATA} as calldata_bytes FROM l2_tx_output WHERE block_timestamp = ${batchTimestamp} AND canonical_chain_batch_number IS NULL - ORDER BY id ASC + ORDER BY block_number ASC, tx_index ASC ` ) @@ -483,30 +482,30 @@ export class DefaultDataService implements DataService { } let totalCalldataBytes: number = 0 - let lastId = -1 + let lastBlockNumber = -1 for (const row of res) { const rowBytes: number = parseInt(row['calldata_bytes'], 10) totalCalldataBytes += rowBytes if (totalCalldataBytes > maxBatchCalldataBytes) { - if (lastId === -1) { - const msg: string = `L2 Tx with ID ${row['id']} has ${totalCalldataBytes} bytes of calldata, which is bigger than the limit of ${maxBatchCalldataBytes}! Cannot roll up this transaction!` + if (lastBlockNumber === -1) { + const msg: string = `L2 Tx with block number ${row['block_number']} has ${totalCalldataBytes} bytes of calldata, which is bigger than the limit of ${maxBatchCalldataBytes}! Cannot roll up this transaction!` log.error(msg) throw Error(msg) } log.debug( `Building Canonical Chain Batch with ${totalCalldataBytes - - rowBytes} bytes of rollup tx calldata and timestamp ${batchTimestamp}. Largest tx output ID: ${lastId}` + rowBytes} bytes of rollup tx calldata and timestamp ${batchTimestamp}. Largest tx output ID: ${lastBlockNumber}` ) - return lastId + return lastBlockNumber } - lastId = parseInt(row['id'], 10) + lastBlockNumber = parseInt(row['block_number'], 10) } log.debug( - `Building Canonical Chain Batch with ${totalCalldataBytes} bytes of rollup tx calldata and timestamp ${batchTimestamp}. Largest tx output ID: ${lastId}` + `Building Canonical Chain Batch with ${totalCalldataBytes} bytes of rollup tx calldata and timestamp ${batchTimestamp}. Largest tx output ID: ${lastBlockNumber}` ) - return lastId + return lastBlockNumber } /** @@ -605,7 +604,7 @@ export class DefaultDataService implements DataService { state_commitment_chain_batch_number = ${batchNumber}, state_commitment_chain_batch_index = t.row_number FROM ( - SELECT id, row_number() over (ORDER BY id) -1 as row_number + SELECT id, row_number() over (ORDER BY block_number ASC, tx_index ASC) -1 as row_number FROM l2_tx_output WHERE state_commitment_chain_batch_number IS NULL ORDER BY block_number ASC, tx_index ASC @@ -670,7 +669,6 @@ export class DefaultDataService implements DataService { FROM ( SELECT id, row_number FROM batchable_l2_only_tx_states - ORDER BY block_number ASC, tx_index ASC LIMIT ${maxBatchSize} ) t WHERE tx.id = t.id`, diff --git a/packages/rollup-core/src/types/data/l2-data-service.ts b/packages/rollup-core/src/types/data/l2-data-service.ts index 95bea813bde29..bcc0b54317432 100644 --- a/packages/rollup-core/src/types/data/l2-data-service.ts +++ b/packages/rollup-core/src/types/data/l2-data-service.ts @@ -31,15 +31,15 @@ export interface L2DataService { ): Promise /** - * Gets the largest L2 Tx Output ID that should be included in the batch built with batchTimestamp. + * Gets the largest L2 Tx Output block number that should be included in the batch built with batchTimestamp. * This is mainly useful as a filter when there is an available batch that has enough rollup transactions' * bytes to exceed the maxBatchCalldataBytes value. * * @param batchTimestamp The block timestamp of the L2 Tx Outputs to be used for the Rollup Batch. * @param maxBatchCalldataBytes The max amount of rolled up tx bytes to include in the batch. - * @returns The ID of the last (biggest ID) L2 Tx Output to be included in the batch. + * @returns The L2 block number of the last (latest) L2 Tx Output to be included in the batch. */ - getMaxL2TxOutputIdForCanonicalChainBatch( + getMaxL2TxOutputBlockNumberForCanonicalChainBatch( batchTimestamp: number, maxBatchCalldataBytes: number ): Promise diff --git a/packages/rollup-core/test/db/helpers.ts b/packages/rollup-core/test/db/helpers.ts index bfd9491871614..16bbc76f0d14b 100644 --- a/packages/rollup-core/test/db/helpers.ts +++ b/packages/rollup-core/test/db/helpers.ts @@ -8,7 +8,7 @@ import { ZERO, ZERO_ADDRESS, } from '@eth-optimism/core-utils' -import { PostgresDB, Row } from '@eth-optimism/core-db' +import { PostgresDB, RDB, Row } from '@eth-optimism/core-db' import { BigNumber as BigNum } from 'ethers/utils' import { Block, TransactionResponse } from 'ethers/providers' @@ -370,3 +370,15 @@ export const insertTxOutput = async ( } } } + +export const selectStateRootBatchRes = async ( + rdb: RDB, + batchNum: number +): Promise => { + return rdb.select( + `SELECT * + FROM l2_tx_output + WHERE state_commitment_chain_batch_number = ${batchNum} + ORDER BY state_commitment_chain_batch_index ASC` + ) +} diff --git a/packages/rollup-core/test/db/l2-data-service.dbspec.ts b/packages/rollup-core/test/db/l2-data-service.dbspec.ts index e3310f35e678d..7e21f9e878f01 100644 --- a/packages/rollup-core/test/db/l2-data-service.dbspec.ts +++ b/packages/rollup-core/test/db/l2-data-service.dbspec.ts @@ -18,6 +18,7 @@ import { getTxSizeInBytes, insertTxOutput, l1Block, + selectStateRootBatchRes, verifyL1BlockRes, verifyL2TxOutput, } from './helpers' @@ -130,10 +131,18 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc }) it('Should build a batch with more than min tx output calldata', async () => { - const tx = createTxOutput(keccak256FromUtf8('tx')) + const tx = createTxOutput( + keccak256FromUtf8('tx'), + keccak256FromUtf8('tx'), + blockNumber + ) await insertTxOutput(dataService, tx) - const tx2 = createTxOutput(keccak256FromUtf8('tx 2')) + const tx2 = createTxOutput( + keccak256FromUtf8('tx 2'), + keccak256FromUtf8('tx 2'), + blockNumber + 1 + ) await insertTxOutput(dataService, tx2) const txSize = Math.min(getTxSizeInBytes(tx), getTxSizeInBytes(tx2)) @@ -242,9 +251,69 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc ) const txRes = await postgres.select( - `SELECT * FROM l2_tx_output WHERE canonical_chain_batch_number = ${batchNum}` + `SELECT * + FROM l2_tx_output + WHERE canonical_chain_batch_number = ${batchNum} + ORDER BY canonical_chain_batch_index ASC` ) txRes.length.should.equal(2, `Should have batched 2 transactions`) + txRes[0]['tx_hash'].should.equal( + tx1.transactionHash, + `Tx 1 should be first tx in batch!` + ) + txRes[1]['tx_hash'].should.equal( + tx2.transactionHash, + `Tx 2 should be second tx in batch!` + ) + }) + + it('Should correctly order transactions', async () => { + const tx1 = createTxOutput( + keccak256FromUtf8('tx 1'), + defaultStateRoot, + blockNumber + ) + tx1.transactionIndex = 1 + const tx2 = createTxOutput( + keccak256FromUtf8('tx 2'), + keccak256FromUtf8(defaultStateRoot), + blockNumber // Note: Same block number + ) + tx2.transactionIndex = 0 + await insertTxOutput(dataService, tx1) + await insertTxOutput(dataService, tx2) + + const txSize = Math.min(getTxSizeInBytes(tx1), getTxSizeInBytes(tx2)) + const batchNum = await dataService.tryBuildCanonicalChainBatchNotPresentOnL1( + txSize, + txSize * 10 + ) + batchNum.should.equal(0, `Batch should have been built`) + + const batchRes = await postgres.select( + `SELECT * FROM canonical_chain_batch` + ) + batchRes.length.should.equal(1, `Batch should exist`) + batchRes[0]['status'].should.equal( + BatchSubmissionStatus.QUEUED, + `Wrong batch status!` + ) + + const txRes = await postgres.select( + `SELECT * + FROM l2_tx_output + WHERE canonical_chain_batch_number = ${batchNum} + ORDER BY canonical_chain_batch_index ASC` + ) + txRes.length.should.equal(2, `Should have batched 2 transactions`) + txRes[0]['tx_hash'].should.equal( + tx2.transactionHash, + `Tx 2 should be first tx in batch!` + ) + txRes[1]['tx_hash'].should.equal( + tx1.transactionHash, + `Tx 1 should be second tx in batch!` + ) }) it('Should build 2 batches, given 2 tx outputs with different timestamps', async () => { @@ -494,7 +563,7 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -507,19 +576,19 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc txs.length.should.equal(2, `Both txos should have been batched!`) txs[0]['state_commitment_chain_batch_index'].should.equal( 0, - `Incorrect tx 1 batch index!` + `Incorrect tx 1 index!` ) txs[0]['tx_hash'].should.equal( tx1.transactionHash, - `Incorrect tx 1 batch index!` + `Incorrect tx 1 hash!` ) txs[1]['state_commitment_chain_batch_index'].should.equal( 1, - `Incorrect tx 2 batch index!` + `Incorrect tx 2 index!` ) txs[1]['tx_hash'].should.equal( tx2.transactionHash, - `Incorrect tx 2 batch index!` + `Incorrect tx 2 index!` ) }) @@ -584,16 +653,14 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) const batchNum = await dataService.tryBuildStateCommitmentChainBatchToMatchAppendedL1Batch() batchNum.should.equal(0, `Batch should have been built`) - const l2TxsBatched = await postgres.select( - `SELECT * FROM l2_tx_output WHERE state_commitment_chain_batch_number = ${l1StateRootBatchNum} ORDER BY block_number ASC, tx_index ASC` - ) + const l2TxsBatched = await selectStateRootBatchRes(postgres, batchNum) l2TxsBatched.length.should.equal( 1, `Only one tx should have been batched!` @@ -709,7 +776,7 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -725,19 +792,19 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc txs.length.should.equal(2, `Both txos should have been batched!`) txs[0]['state_commitment_chain_batch_index'].should.equal( 0, - `Incorrect tx 1 batch index!` + `Incorrect tx 1 index!` ) txs[0]['tx_hash'].should.equal( tx1.transactionHash, - `Incorrect tx 1 batch index!` + `Incorrect tx 1 hash!` ) txs[1]['state_commitment_chain_batch_index'].should.equal( 1, - `Incorrect tx 2 batch index!` + `Incorrect tx 2 index!` ) txs[1]['tx_hash'].should.equal( tx2.transactionHash, - `Incorrect tx 2 batch index!` + `Incorrect tx 2 hash!` ) }) @@ -752,8 +819,55 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, + blockNumber + 1 + ) + await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) + + let batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch( + 1, + 1 + ) + batchNum.should.equal(0, `Batch should have been built`) + + let stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + stateBatchRes.length.should.equal( + 1, + `First txo should have been batched!` + ) + stateBatchRes[0]['tx_hash'].should.equal( + tx1.transactionHash, + `first batch should be tx 1!` + ) + + batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch(1, 1) + batchNum.should.equal(1, `Batch should have been built`) + + stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + stateBatchRes.length.should.equal( + 1, + `Second txo should have been batched!` + ) + stateBatchRes[0]['tx_hash'].should.equal( + tx2.transactionHash, + `second batch should be tx 2!` + ) + }) + + it('Should order state commitment batches by block number and then by tx index (1 of 2)', async () => { + const tx1 = createTxOutput( + keccak256FromUtf8('tx 1'), + defaultStateRoot, blockNumber ) + tx1.transactionIndex = 0 + await insertTxOutput(dataService, tx1, BatchSubmissionStatus.FINALIZED) + + const tx2 = createTxOutput( + keccak256FromUtf8('tx 2'), + defaultStateRoot, + blockNumber // Intentionally the same block number + ) + tx2.transactionIndex = 1 await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) let batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch( @@ -762,11 +876,13 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc ) batchNum.should.equal(0, `Batch should have been built`) - let count = await postgres.select( - `SELECT * FROM l2_tx_output WHERE state_commitment_chain_batch_number = ${batchNum}` + let stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + + stateBatchRes.length.should.equal( + 1, + `First txo should have been batched!` ) - count.length.should.equal(1, `First txo should have been batched!`) - count[0]['tx_hash'].should.equal( + stateBatchRes[0]['tx_hash'].should.equal( tx1.transactionHash, `first batch should be tx 1!` ) @@ -774,15 +890,66 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch(1, 1) batchNum.should.equal(1, `Batch should have been built`) - count = await postgres.select( - `SELECT * FROM l2_tx_output WHERE state_commitment_chain_batch_number = ${batchNum}` + stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + + stateBatchRes.length.should.equal( + 1, + `Second txo should have been batched!` ) - count.length.should.equal(1, `Second txo should have been batched!`) - count[0]['tx_hash'].should.equal( + stateBatchRes[0]['tx_hash'].should.equal( tx2.transactionHash, `second batch should be tx 2!` ) }) + + it('Should order state commitment batches by block number and then by tx index (2 of 2)', async () => { + const tx1 = createTxOutput( + keccak256FromUtf8('tx 1'), + defaultStateRoot, + blockNumber + ) + tx1.transactionIndex = 1 + await insertTxOutput(dataService, tx1, BatchSubmissionStatus.FINALIZED) + + const tx2 = createTxOutput( + keccak256FromUtf8('tx 2'), + defaultStateRoot, + blockNumber // Intentionally the same block number + ) + tx2.transactionIndex = 0 + await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) + + let batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch( + 1, + 1 + ) + batchNum.should.equal(0, `Batch should have been built`) + + let stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + + stateBatchRes.length.should.equal( + 1, + `First txo should have been batched!` + ) + stateBatchRes[0]['tx_hash'].should.equal( + tx2.transactionHash, + `first batch should be tx 2!` + ) + + batchNum = await dataService.tryBuildL2OnlyStateCommitmentChainBatch(1, 1) + batchNum.should.equal(1, `Batch should have been built`) + + stateBatchRes = await selectStateRootBatchRes(postgres, batchNum) + + stateBatchRes.length.should.equal( + 1, + `Second txo should have been batched!` + ) + stateBatchRes[0]['tx_hash'].should.equal( + tx1.transactionHash, + `second batch should be tx 1!` + ) + }) }) describe('getNextCanonicalChainTransactionBatchToSubmit', () => { @@ -858,7 +1025,7 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.QUEUED) @@ -946,7 +1113,7 @@ describe('L2 Data Service (will fail if postgres is not running with expected sc const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), defaultStateRoot, - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.SENT) diff --git a/packages/rollup-core/test/db/verifier-data-service.dbspec.ts b/packages/rollup-core/test/db/verifier-data-service.dbspec.ts index 66977a8c5618e..6909af263e3f2 100644 --- a/packages/rollup-core/test/db/verifier-data-service.dbspec.ts +++ b/packages/rollup-core/test/db/verifier-data-service.dbspec.ts @@ -1,31 +1,21 @@ import '../setup' /* External Imports */ -import { PostgresDB, Row } from '@eth-optimism/core-db' +import { PostgresDB } from '@eth-optimism/core-db' import { keccak256FromUtf8 } from '@eth-optimism/core-utils' /* Internal Imports */ import { DefaultDataService } from '../../src/app/data' import { blockNumber, - createRollupTx, createTx, createTxOutput, defaultStateRoot, deleteAllData, insertTxOutput, l1Block, - verifyL1BlockRes, - verifyL2TxOutput, } from './helpers' -import { - BatchSubmissionStatus, - QueueOrigin, - StateCommitmentBatchSubmission, - TransactionBatchSubmission, - VerificationStatus, -} from '../../src/types/data' -import { VerificationCandidate } from '../../src/types' +import { BatchSubmissionStatus, VerificationStatus } from '../../src/types/data' describe('Verifier Data Data Service (will fail if postgres is not running with expected schema)', () => { let dataService: DefaultDataService @@ -111,7 +101,7 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -156,7 +146,7 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -198,7 +188,7 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -240,7 +230,7 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) @@ -285,13 +275,13 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx2 = createTxOutput( keccak256FromUtf8('tx 2'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 1 ) await insertTxOutput(dataService, tx2, BatchSubmissionStatus.FINALIZED) const tx3 = createTxOutput( keccak256FromUtf8('tx 3'), keccak256FromUtf8(defaultStateRoot), - blockNumber + blockNumber + 2 ) await insertTxOutput(dataService, tx3, BatchSubmissionStatus.FINALIZED) @@ -307,14 +297,14 @@ describe('Verifier Data Data Service (will fail if postgres is not running with const tx4 = createTxOutput( keccak256FromUtf8('tx 4'), stateRoot4, - blockNumber + blockNumber + 3 ) await insertTxOutput(dataService, tx4, BatchSubmissionStatus.FINALIZED) const tx5 = createTxOutput( keccak256FromUtf8('tx 5'), stateRoot5, - blockNumber + blockNumber + 4 ) await insertTxOutput(dataService, tx5, BatchSubmissionStatus.FINALIZED)