Commit e28cec75 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

dtl: fix replica tx indexing (#872)

* dtl: update constant to 9 million gaslimit

* dtl: parse as rlp without top level type

* chore: add changeset

* test: delete dead code

* lint: fix

* deps: add etherproject transactions

* dtl: better handler for more number types

* tests: fix chainid
parent 0c168053
---
'@eth-optimism/data-transport-layer': patch
---
Fixes a bug where L2 synced transactions were not RLP encoded
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"@eth-optimism/contracts": "^0.3.0", "@eth-optimism/contracts": "^0.3.0",
"@eth-optimism/core-utils": "^0.4.2", "@eth-optimism/core-utils": "^0.4.2",
"@ethersproject/providers": "^5.0.21", "@ethersproject/providers": "^5.0.21",
"@ethersproject/transactions": "^5.0.21",
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
"@sentry/tracing": "^6.3.1", "@sentry/tracing": "^6.3.1",
"@types/express": "^4.17.11", "@types/express": "^4.17.11",
......
...@@ -104,7 +104,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet< ...@@ -104,7 +104,7 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
nextTxPointer nextTxPointer
) )
const { decoded, type } = maybeDecodeSequencerBatchTransaction( const decoded = maybeDecodeSequencerBatchTransaction(
sequencerTransaction sequencerTransaction
) )
...@@ -120,7 +120,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet< ...@@ -120,7 +120,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
origin: null, origin: null,
data: toHexString(sequencerTransaction), data: toHexString(sequencerTransaction),
queueOrigin: 'sequencer', queueOrigin: 'sequencer',
type,
value: decoded ? decoded.value : '0x0', value: decoded ? decoded.value : '0x0',
queueIndex: null, queueIndex: null,
decoded, decoded,
...@@ -153,7 +152,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet< ...@@ -153,7 +152,6 @@ export const handleEventsSequencerBatchAppended: EventHandlerSet<
origin: constants.AddressZero, origin: constants.AddressZero,
data: '0x', data: '0x',
queueOrigin: 'l1', queueOrigin: 'l1',
type: 'EIP155',
value: '0x0', value: '0x0',
queueIndex: queueIndex.toNumber(), queueIndex: queueIndex.toNumber(),
decoded: null, decoded: null,
...@@ -239,52 +237,24 @@ const parseSequencerBatchTransaction = ( ...@@ -239,52 +237,24 @@ const parseSequencerBatchTransaction = (
const maybeDecodeSequencerBatchTransaction = ( const maybeDecodeSequencerBatchTransaction = (
transaction: Buffer transaction: Buffer
): { ): DecodedSequencerBatchTransaction | null => {
decoded: DecodedSequencerBatchTransaction | null
type: 'EIP155' | 'ETH_SIGN' | null
} => {
try { try {
const decodedTx = ethers.utils.parseTransaction(transaction) const decodedTx = ethers.utils.parseTransaction(transaction)
return { return {
type: 'EIP155', nonce: BigNumber.from(decodedTx.nonce).toNumber(),
decoded: { gasPrice: BigNumber.from(decodedTx.gasPrice).toNumber(),
nonce: BigNumber.from(decodedTx.nonce).toNumber(), gasLimit: BigNumber.from(decodedTx.gasLimit).toNumber(),
gasPrice: BigNumber.from(decodedTx.gasPrice).toNumber(), value: toRpcHexString(decodedTx.value),
gasLimit: BigNumber.from(decodedTx.gasLimit).toNumber(), target: toHexString(decodedTx.to), // Maybe null this out for creations?
value: toRpcHexString(decodedTx.value), data: toHexString(decodedTx.data),
target: toHexString(decodedTx.to), // Maybe null this out for creations? sig: {
data: toHexString(decodedTx.data), v: BigNumber.from(decodedTx.v).toNumber(),
sig: { r: toHexString(decodedTx.r),
v: BigNumber.from(decodedTx.v).toNumber(), s: toHexString(decodedTx.s),
r: toHexString(decodedTx.r),
s: toHexString(decodedTx.s),
},
type: 0, // EIP155 legacy holdover.
}, },
} }
} catch (err) { } catch (err) {
return { return null
decoded: null,
type: null,
}
}
}
export function validateBatchTransaction(
type: string | null,
decoded: DecodedSequencerBatchTransaction | null
): boolean {
// Unknown types are considered invalid
if (type === null) {
return false
}
if (type === 'EIP155' || type === 'ETH_SIGN') {
if (decoded.sig.v !== 1 && decoded.sig.v !== 0) {
return false
}
return true
} }
// Allow soft forks
return false
} }
/* Imports: External */ /* Imports: External */
import { ctcCoder } from '@eth-optimism/core-utils'
import { BigNumber, constants, ethers } from 'ethers' import { BigNumber, constants, ethers } from 'ethers'
import { serialize } from '@ethersproject/transactions'
/* Imports: Internal */ /* Imports: Internal */
import { TransportDB } from '../../../db/transport-db' import { TransportDB } from '../../../db/transport-db'
...@@ -37,7 +37,6 @@ export const handleSequencerBlock = { ...@@ -37,7 +37,6 @@ export const handleSequencerBlock = {
blockNumber: BigNumber.from(transaction.l1BlockNumber).toNumber(), blockNumber: BigNumber.from(transaction.l1BlockNumber).toNumber(),
timestamp: BigNumber.from(transaction.l1Timestamp).toNumber(), timestamp: BigNumber.from(transaction.l1Timestamp).toNumber(),
queueOrigin: transaction.queueOrigin, queueOrigin: transaction.queueOrigin,
type: parseTxType(transaction.txType),
confirmed: false, confirmed: false,
} }
...@@ -54,7 +53,6 @@ export const handleSequencerBlock = { ...@@ -54,7 +53,6 @@ export const handleSequencerBlock = {
nonce: BigNumber.from(transaction.nonce).toNumber(), nonce: BigNumber.from(transaction.nonce).toNumber(),
target: transaction.to || constants.AddressZero, // ? target: transaction.to || constants.AddressZero, // ?
data: transaction.input, data: transaction.input,
type: transaction.txType,
} }
transactionEntry = { transactionEntry = {
...@@ -62,9 +60,21 @@ export const handleSequencerBlock = { ...@@ -62,9 +60,21 @@ export const handleSequencerBlock = {
gasLimit: SEQUENCER_GAS_LIMIT, // ? gasLimit: SEQUENCER_GAS_LIMIT, // ?
target: SEQUENCER_ENTRYPOINT_ADDRESS, target: SEQUENCER_ENTRYPOINT_ADDRESS,
origin: null, origin: null,
data: maybeEncodeSequencerBatchTransaction( data: serialize(
decodedTransaction, {
transaction.txType value: transaction.value,
gasLimit: transaction.gas,
gasPrice: transaction.gasPrice,
nonce: transaction.nonce,
to: transaction.to || constants.AddressZero,
data: transaction.input,
chainId,
},
{
v: BigNumber.from(transaction.v).toNumber(),
r: padHexString(transaction.r, 32),
s: padHexString(transaction.s, 32),
}
), ),
decoded: decodedTransaction, decoded: decodedTransaction,
queueIndex: null, queueIndex: null,
...@@ -111,37 +121,3 @@ export const handleSequencerBlock = { ...@@ -111,37 +121,3 @@ export const handleSequencerBlock = {
await db.putUnconfirmedStateRootEntries([entry.stateRootEntry]) await db.putUnconfirmedStateRootEntries([entry.stateRootEntry])
}, },
} }
/**
* Attempts to encode a sequencer batch transaction.
* @param transaction Transaction to encode.
* @param type Transaction type.
*/
const maybeEncodeSequencerBatchTransaction = (
transaction: DecodedSequencerBatchTransaction,
type: 'EIP155' | 'EthSign' | null
): string => {
if (type === 'EIP155') {
return ctcCoder.eip155TxData.encode(transaction).toLowerCase()
} else if (type === 'EthSign') {
return ctcCoder.ethSignTxData.encode(transaction).toLowerCase()
} else {
// Throw?
return
}
}
/**
* Handles differences between the sequencer's enum strings and our own.
* Will probably want to move this into core-utils eventually.
* @param type Sequencer transaction type to parse.
*/
const parseTxType = (
type: 'EIP155' | 'EthSign' | null
): 'EIP155' | 'ETH_SIGN' | null => {
if (type === 'EthSign') {
return 'ETH_SIGN'
} else {
return type
}
}
...@@ -10,7 +10,6 @@ export interface DecodedSequencerBatchTransaction { ...@@ -10,7 +10,6 @@ export interface DecodedSequencerBatchTransaction {
nonce: number nonce: number
target: string target: string
data: string data: string
type: number
} }
export interface EnqueueEntry { export interface EnqueueEntry {
...@@ -35,7 +34,6 @@ export interface TransactionEntry { ...@@ -35,7 +34,6 @@ export interface TransactionEntry {
value: string value: string
queueOrigin: 'sequencer' | 'l1' queueOrigin: 'sequencer' | 'l1'
queueIndex: number | null queueIndex: number | null
type: 'EIP155' | 'ETH_SIGN' | null
decoded: DecodedSequencerBatchTransaction | null decoded: DecodedSequencerBatchTransaction | null
confirmed: boolean confirmed: boolean
} }
......
export const SEQUENCER_GAS_LIMIT = 8_000_000 // TODO: Remove and use value from event. export const SEQUENCER_GAS_LIMIT = 9_000_000 // TODO: Remove and use value from event.
export const SEQUENCER_ENTRYPOINT_ADDRESS = export const SEQUENCER_ENTRYPOINT_ADDRESS =
'0x4200000000000000000000000000000000000005' '0x4200000000000000000000000000000000000005'
import { BigNumber, ethers } from 'ethers' import { BigNumber, ethers } from 'ethers'
import { expect } from '../../../../setup' import { expect } from '../../../../setup'
import { import { handleEventsSequencerBatchAppended } from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended'
validateBatchTransaction,
handleEventsSequencerBatchAppended,
} from '../../../../../src/services/l1-ingestion/handlers/sequencer-batch-appended'
import { SequencerBatchAppendedExtraData } from '../../../../../src/types' import { SequencerBatchAppendedExtraData } from '../../../../../src/types'
import { l1TransactionData } from '../../../examples/l1-data' import { l1TransactionData } from '../../../examples/l1-data'
import { blocksOnL2 } from '../../../examples/l2-data' import { blocksOnL2 } from '../../../examples/l2-data'
describe('Event Handlers: OVM_CanonicalTransactionChain.SequencerBatchAppended', () => { describe('Event Handlers: OVM_CanonicalTransactionChain.SequencerBatchAppended', () => {
describe('validateBatchTransaction', () => {
it('should mark a transaction as invalid if the type is null', () => {
const input1: [any, any] = [null, null]
const output1 = validateBatchTransaction(...input1)
const expected1 = false
expect(output1).to.equal(expected1)
})
it('should mark a transaction as invalid if the type is not EIP155 or ETH_SIGN', () => {
const input1: [any, any] = ['SOME_RANDOM_TYPE', null]
const output1 = validateBatchTransaction(...input1)
const expected1 = false
expect(output1).to.equal(expected1)
})
describe('when the transaction type is EIP155 or ETH_SIGN', () => {
it('should mark a transaction as valid if the `v` parameter is 0', () => {
// CTC index 23159
const input1: [any, any] = [
'EIP155',
{
sig: {
v: 0,
},
},
]
const output1 = validateBatchTransaction(...input1)
const expected1 = true
expect(output1).to.equal(expected1)
})
it('should mark a transaction as valid if the `v` parameter is 1', () => {
// CTC index 23159
const input1: [any, any] = [
'EIP155',
{
sig: {
v: 1,
},
},
]
const output1 = validateBatchTransaction(...input1)
const expected1 = true
expect(output1).to.equal(expected1)
})
it('should mark a transaction as invalid if the `v` parameter is greater than 1', () => {
// CTC index 23159
const input1: [any, any] = [
'EIP155',
{
sig: {
v: 2,
},
},
]
const output1 = validateBatchTransaction(...input1)
const expected1 = false
expect(output1).to.equal(expected1)
})
})
describe('regressions', () => {
it('should catch the invalid transaction', () => {
// CTC index 23159
const input1: [any, any] = [
'EIP155',
{
sig: {
r:
'0x0fbef2080fadc4198ee0d6027e2eb70799d3418574cc085c34a14dcefe14d5d3',
s:
'0x3bf394a7cb2aca6790e67382f782a406aefce7553212db52b54a4e087c2195ad',
v: 56,
},
gasLimit: 8000000,
gasPrice: 0,
nonce: 0,
target: '0x1111111111111111111111111111111111111111',
data: '0x1234',
},
]
const output1 = validateBatchTransaction(...input1)
const expected1 = false
expect(output1).to.equal(expected1)
})
})
})
describe('handleEventsSequencerBatchAppended.parseEvent', () => { describe('handleEventsSequencerBatchAppended.parseEvent', () => {
// This tests the behavior of parsing a real mainnet transaction, // This tests the behavior of parsing a real mainnet transaction,
// so it will break if the encoding scheme changes. // so it will break if the encoding scheme changes.
......
...@@ -5,7 +5,7 @@ import { handleSequencerBlock } from '../../../../../src/services/l2-ingestion/h ...@@ -5,7 +5,7 @@ import { handleSequencerBlock } from '../../../../../src/services/l2-ingestion/h
describe('Handlers: handleSequencerBlock', () => { describe('Handlers: handleSequencerBlock', () => {
describe('parseBlock', () => { describe('parseBlock', () => {
it('should correctly extract key fields from an L2 mainnet transaction', async () => { it('should correctly extract key fields from an L2 mainnet transaction', async () => {
const input1: [any, number] = [l2Block, 420] const input1: [any, number] = [l2Block, 10]
const output1 = await handleSequencerBlock.parseBlock(...input1) const output1 = await handleSequencerBlock.parseBlock(...input1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment