Commit dee74ef5 authored by Annie Ke's avatar Annie Ke Committed by GitHub

[cleanup] batch submitter unused code + types to core-utils (#614)

* remove unused utils files

* remove duplicate spec file

* remove unused imports

* move batch-submitter types to core-utils

* add changeset
parent bebeef14
---
"@eth-optimism/batch-submitter": patch
"@eth-optimism/core-utils": patch
---
migrate batch submitter types to core-utils
...@@ -2,21 +2,9 @@ ...@@ -2,21 +2,9 @@
import { Contract, Signer, utils, providers } from 'ethers' import { Contract, Signer, utils, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider' import { TransactionReceipt } from '@ethersproject/abstract-provider'
import * as ynatm from '@eth-optimism/ynatm' import * as ynatm from '@eth-optimism/ynatm'
import { Logger, Metrics } from '@eth-optimism/core-utils' import { RollupInfo, Logger, Metrics } from '@eth-optimism/core-utils'
import { getContractFactory } from 'old-contracts' import { getContractFactory } from 'old-contracts'
export interface RollupInfo {
mode: 'sequencer' | 'verifier'
syncing: boolean
ethContext: {
blockNumber: number
timestamp: number
}
rollupContext: {
index: number
queueIndex: number
}
}
export interface Range { export interface Range {
start: number start: number
end: number end: number
......
...@@ -4,16 +4,16 @@ import { Contract, Signer, providers } from 'ethers' ...@@ -4,16 +4,16 @@ import { Contract, Signer, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider' import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { getContractFactory } from 'old-contracts' import { getContractFactory } from 'old-contracts'
import { import {
L2Block,
RollupInfo,
Logger, Logger,
Bytes32, Bytes32,
remove0x, remove0x,
toRpcHexString,
Metrics, Metrics,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
/* Internal Imports */ /* Internal Imports */
import { L2Block } from '..' import { Range, BatchSubmitter, BLOCK_OFFSET } from '.'
import { RollupInfo, Range, BatchSubmitter, BLOCK_OFFSET } from '.'
export class StateBatchSubmitter extends BatchSubmitter { export class StateBatchSubmitter extends BatchSubmitter {
// TODO: Change this so that we calculate start = scc.totalElements() and end = ctc.totalElements()! // TODO: Change this so that we calculate start = scc.totalElements() and end = ctc.totalElements()!
......
/* External Imports */ /* External Imports */
import { Promise as bPromise } from 'bluebird' import { Promise as bPromise } from 'bluebird'
import { Signer, ethers, Contract, providers } from 'ethers' import { Signer, ethers, Contract, providers } from 'ethers'
import { import { TransactionReceipt } from '@ethersproject/abstract-provider'
TransactionResponse,
TransactionReceipt,
} from '@ethersproject/abstract-provider'
import { getContractInterface, getContractFactory } from 'old-contracts' import { getContractInterface, getContractFactory } from 'old-contracts'
import { getContractInterface as getNewContractInterface } from '@eth-optimism/contracts' import { getContractInterface as getNewContractInterface } from '@eth-optimism/contracts'
import { Logger, Metrics, ctcCoder } from '@eth-optimism/core-utils' import {
L2Block,
RollupInfo,
BatchElement,
Batch,
QueueOrigin,
Logger,
Metrics,
} from '@eth-optimism/core-utils'
/* Internal Imports */ /* Internal Imports */
import { import {
...@@ -17,8 +22,7 @@ import { ...@@ -17,8 +22,7 @@ import {
AppendSequencerBatchParams, AppendSequencerBatchParams,
} from '../transaction-chain-contract' } from '../transaction-chain-contract'
import { L2Block, BatchElement, Batch, QueueOrigin } from '..' import { Range, BatchSubmitter, BLOCK_OFFSET } from '.'
import { RollupInfo, Range, BatchSubmitter, BLOCK_OFFSET } from '.'
export interface AutoFixBatchOptions { export interface AutoFixBatchOptions {
fixDoublePlayedDeposits: boolean fixDoublePlayedDeposits: boolean
......
export * from './batch-submitter' export * from './batch-submitter'
export * from './transaction-chain-contract' export * from './transaction-chain-contract'
export * from './types'
export * from './utils'
/* External Imports */
import { BigNumber } from 'ethers'
export const getLen = (pos: { start; end }) => (pos.end - pos.start) * 2
export const encodeHex = (val: any, len: number) =>
remove0x(BigNumber.from(val).toHexString()).padStart(len, '0')
export const toVerifiedBytes = (val: string, len: number) => {
val = remove0x(val)
if (val.length !== len) {
throw new Error('Invalid length!')
}
return val
}
export const remove0x = (str: string): string => {
if (str.startsWith('0x')) {
return str.slice(2)
} else {
return str
}
}
...@@ -22,19 +22,18 @@ import { ...@@ -22,19 +22,18 @@ import {
} from '../helpers' } from '../helpers'
import { import {
CanonicalTransactionChainContract, CanonicalTransactionChainContract,
QueueOrigin,
TransactionBatchSubmitter as RealTransactionBatchSubmitter, TransactionBatchSubmitter as RealTransactionBatchSubmitter,
StateBatchSubmitter, StateBatchSubmitter,
TX_BATCH_SUBMITTER_LOG_TAG, TX_BATCH_SUBMITTER_LOG_TAG,
STATE_BATCH_SUBMITTER_LOG_TAG, STATE_BATCH_SUBMITTER_LOG_TAG,
Batch,
BatchSubmitter, BatchSubmitter,
} from '../../src' } from '../../src'
import { import {
QueueOrigin,
Batch,
Signature, Signature,
TxType, TxType,
ctcCoder,
remove0x, remove0x,
Logger, Logger,
Metrics, Metrics,
......
...@@ -4,9 +4,7 @@ import { ...@@ -4,9 +4,7 @@ import {
BlockWithTransactions, BlockWithTransactions,
TransactionResponse, TransactionResponse,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
import { L2Transaction, L2Block, RollupInfo } from '@eth-optimism/core-utils'
/* Internal Imports */
import { L2Transaction, L2Block, RollupInfo } from '../../src'
/** /**
* Unformatted Transaction & Blocks. This exists because Geth currently * Unformatted Transaction & Blocks. This exists because Geth currently
......
import { expect, chai } from '../setup'
/* External Imports */
import { ethers } from 'hardhat'
import '@nomiclabs/hardhat-ethers'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import ganache from 'ganache-core'
import sinon from 'sinon'
import { Web3Provider, JsonRpcProvider } from '@ethersproject/providers'
import { getContractInterface } from 'old-contracts'
import { smockit, MockContract } from '@eth-optimism/smock'
/* Internal Imports */
import { MockchainProvider } from './mockchain-provider'
import {
makeAddressManager,
setProxyTarget,
FORCE_INCLUSION_PERIOD_SECONDS,
getContractFactory,
} from '../helpers'
import {
CanonicalTransactionChainContract,
QueueOrigin,
TransactionBatchSubmitter as RealTransactionBatchSubmitter,
TX_BATCH_SUBMITTER_LOG_TAG,
Batch,
BatchSubmitter,
} from '../../src'
import {
Signature,
TxType,
ctcCoder,
remove0x,
Logger,
Metrics,
} from '@eth-optimism/core-utils'
const DECOMPRESSION_ADDRESS = '0x4200000000000000000000000000000000000008'
const MAX_GAS_LIMIT = 8_000_000
const MAX_TX_SIZE = 100_000
const MIN_TX_SIZE = 1_000
const MIN_GAS_PRICE_IN_GWEI = 1
const MAX_GAS_PRICE_IN_GWEI = 70
const GAS_RETRY_INCREMENT = 5
const GAS_THRESHOLD_IN_GWEI = 120
// Helper functions
interface QueueElement {
queueRoot: string
timestamp: number
blockNumber: number
}
const getQueueElement = async (
ctcContract: Contract,
nextQueueIndex?: number
): Promise<QueueElement> => {
if (!nextQueueIndex) {
nextQueueIndex = await ctcContract.getNextQueueIndex()
}
const nextQueueElement = await ctcContract.getQueueElement(nextQueueIndex)
return nextQueueElement
}
const DUMMY_SIG: Signature = {
r: '11'.repeat(32),
s: '22'.repeat(32),
v: 1,
}
// A transaction batch submitter which skips the validate batch check
class TransactionBatchSubmitter extends RealTransactionBatchSubmitter {
protected async _validateBatch(batch: Batch): Promise<boolean> {
return true
}
}
const testMetrics = new Metrics({ prefix: 'tx_bs_test' })
describe('TransactionBatchSubmitter', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
;[signer, sequencer] = await ethers.getSigners()
})
let AddressManager: Contract
let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_StateCommitmentChain: MockContract
before(async () => {
AddressManager = await makeAddressManager()
await AddressManager.setAddress(
'OVM_Sequencer',
await sequencer.getAddress()
)
await AddressManager.setAddress(
'OVM_DecompressionPrecompileAddress',
DECOMPRESSION_ADDRESS
)
Mock__OVM_ExecutionManager = await smockit(
await getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_StateCommitmentChain = await smockit(
await getContractFactory('OVM_StateCommitmentChain')
)
await setProxyTarget(
AddressManager,
'OVM_ExecutionManager',
Mock__OVM_ExecutionManager
)
await setProxyTarget(
AddressManager,
'OVM_StateCommitmentChain',
Mock__OVM_StateCommitmentChain
)
Mock__OVM_StateCommitmentChain.smocked.canOverwrite.will.return.with(false)
Mock__OVM_ExecutionManager.smocked.getMaxTransactionGasLimit.will.return.with(
MAX_GAS_LIMIT
)
})
let Factory__OVM_CanonicalTransactionChain: ContractFactory
before(async () => {
Factory__OVM_CanonicalTransactionChain = await getContractFactory(
'OVM_CanonicalTransactionChain'
)
})
let OVM_CanonicalTransactionChain: CanonicalTransactionChainContract
let l2Provider: MockchainProvider
beforeEach(async () => {
const unwrapped_OVM_CanonicalTransactionChain = await Factory__OVM_CanonicalTransactionChain.deploy(
AddressManager.address,
FORCE_INCLUSION_PERIOD_SECONDS
)
await unwrapped_OVM_CanonicalTransactionChain.init()
await AddressManager.setAddress(
'OVM_CanonicalTransactionChain',
unwrapped_OVM_CanonicalTransactionChain.address
)
OVM_CanonicalTransactionChain = new CanonicalTransactionChainContract(
unwrapped_OVM_CanonicalTransactionChain.address,
getContractInterface('OVM_CanonicalTransactionChain'),
sequencer
)
l2Provider = new MockchainProvider(
OVM_CanonicalTransactionChain.address,
'0x' + '00'.repeat(20)
)
})
afterEach(() => {
sinon.restore()
})
describe('Submit', () => {
const enqueuedElements: Array<{
blockNumber: number
timestamp: number
}> = []
let batchSubmitter
beforeEach(async () => {
for (let i = 1; i < 15; i++) {
await OVM_CanonicalTransactionChain.enqueue(
'0x' + '01'.repeat(20),
50_000,
'0x' + i.toString().repeat(64),
{
gasLimit: 1_000_000,
}
)
}
batchSubmitter = new TransactionBatchSubmitter(
sequencer,
l2Provider as any,
MIN_TX_SIZE,
MAX_TX_SIZE,
10,
0,
1,
100000,
AddressManager.address,
1,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
false
)
})
it('should submit a sequencer batch correctly', async () => {
l2Provider.setNumBlocksToReturn(5)
const nextQueueElement = await getQueueElement(
OVM_CanonicalTransactionChain
)
const data = ctcCoder.eip155TxData.encode({
sig: DUMMY_SIG,
gasLimit: 0,
gasPrice: 0,
nonce: 0,
target: '0x0000000000000000000000000000000000000000',
data: '0x',
type: TxType.EIP155,
})
l2Provider.setL2BlockData(
{
data,
l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: TxType.EIP155,
queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1
)
let receipt = await batchSubmitter.submitNextBatch()
let logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(0) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(6) // _totalElements
receipt = await batchSubmitter.submitNextBatch()
logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(0) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a queue batch correctly', async () => {
l2Provider.setNumBlocksToReturn(5)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
let receipt = await batchSubmitter.submitNextBatch()
let logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(6) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(6) // _totalElements
receipt = await batchSubmitter.submitNextBatch()
logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(6) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(5) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a batch with both queue and sequencer chain elements', async () => {
l2Provider.setNumBlocksToReturn(10) // For this batch we'll return 10 elements!
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
// Turn blocks 3-5 into sequencer txs
const nextQueueElement = await getQueueElement(
OVM_CanonicalTransactionChain,
2
)
const data = ctcCoder.ethSignTxData.encode({
sig: DUMMY_SIG,
gasLimit: 0,
gasPrice: 0,
nonce: 0,
target: '0x0000000000000000000000000000000000000000',
data: '0x',
type: TxType.EthSign,
})
l2Provider.setL2BlockData(
{
data,
l1BlockNumber: nextQueueElement.blockNumber - 1,
txType: TxType.EthSign,
queueOrigin: QueueOrigin.Sequencer,
l1TxOrigin: null,
} as any,
nextQueueElement.timestamp - 1,
'', // blank state root
3,
6
)
const receipt = await batchSubmitter.submitNextBatch()
const logData = remove0x(receipt.logs[1].data)
expect(parseInt(logData.slice(64 * 0, 64 * 1), 16)).to.equal(0) // _startingQueueIndex
expect(parseInt(logData.slice(64 * 1, 64 * 2), 16)).to.equal(8) // _numQueueElements
expect(parseInt(logData.slice(64 * 2, 64 * 3), 16)).to.equal(11) // _totalElements
})
it('should submit a small batch only after the timeout', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
const createBatchSubmitter = (
timeout: number
): TransactionBatchSubmitter =>
new TransactionBatchSubmitter(
sequencer,
l2Provider as any,
MIN_TX_SIZE,
MAX_TX_SIZE,
10,
timeout,
1,
100000,
AddressManager.address,
1,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
false
)
// Create a batch submitter with a long timeout & make sure it doesn't submit the batches one after another
const longTimeout = 10_000
batchSubmitter = createBatchSubmitter(longTimeout)
let receipt = await batchSubmitter.submitNextBatch()
expect(receipt).to.not.be.undefined
receipt = await batchSubmitter.submitNextBatch()
// The receipt should be undefined because that means it didn't submit
expect(receipt).to.be.undefined
// This time create a batch submitter with a short timeout & it should submit batches after the timeout is reached
const shortTimeout = 5
batchSubmitter = createBatchSubmitter(shortTimeout)
receipt = await batchSubmitter.submitNextBatch()
expect(receipt).to.not.be.undefined
// Sleep for the short timeout
await new Promise((r) => setTimeout(r, shortTimeout))
receipt = await batchSubmitter.submitNextBatch()
// The receipt should NOT be undefined because that means it successfully submitted!
expect(receipt).to.not.be.undefined
})
it('should not submit if gas price is over threshold', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
const highGasPriceWei = BigNumber.from(200).mul(1_000_000_000)
sinon
.stub(sequencer, 'getGasPrice')
.callsFake(async () => highGasPriceWei)
const receipt = await batchSubmitter.submitNextBatch()
expect(sequencer.getGasPrice).to.have.been.calledOnce
expect(receipt).to.be.undefined
})
it('should submit if gas price is not over threshold', async () => {
l2Provider.setNumBlocksToReturn(2)
l2Provider.setL2BlockData({
queueOrigin: QueueOrigin.L1ToL2,
} as any)
const lowGasPriceWei = BigNumber.from(2).mul(1_000_000_000)
sinon.stub(sequencer, 'getGasPrice').callsFake(async () => lowGasPriceWei)
const receipt = await batchSubmitter.submitNextBatch()
expect(sequencer.getGasPrice).to.have.been.calledOnce
expect(receipt).to.not.be.undefined
})
})
})
describe('TransactionBatchSubmitter to Ganache', () => {
let signer
const server = ganache.server({
default_balance_ether: 420,
blockTime: 2_000,
})
const provider = new Web3Provider(ganache.provider())
before(async () => {
await server.listen(3001)
signer = await provider.getSigner()
})
after(async () => {
await server.close()
})
// Unit test for getReceiptWithResubmission function,
// tests for increasing gas price on resubmission
it('should resubmit a transaction if it is not confirmed', async () => {
const gasPrices = []
const numConfirmations = 2
const sendTxFunc = async (gasPrice) => {
// push the retried gasPrice
gasPrices.push(gasPrice)
const tx = signer.sendTransaction({
to: DECOMPRESSION_ADDRESS,
value: 88,
nonce: 0,
gasPrice,
})
const response = await tx
return signer.provider.waitForTransaction(response.hash, numConfirmations)
}
const resubmissionConfig = {
numConfirmations,
resubmissionTimeout: 1_000, // retry every second
minGasPriceInGwei: 0,
maxGasPriceInGwei: 100,
gasRetryIncrement: 5,
}
BatchSubmitter.getReceiptWithResubmission(
sendTxFunc,
resubmissionConfig,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG })
)
// Wait 1.5s for at least 1 retry
await new Promise((r) => setTimeout(r, 1500))
// Iterate through gasPrices to ensure each entry increases from
// the last
const isIncreasing = gasPrices.reduce(
(isInc, gasPrice, i, gP) =>
(isInc && gasPrice > gP[i - 1]) || Number.NEGATIVE_INFINITY,
true
)
expect(gasPrices).to.have.lengthOf.above(1) // retried at least once
expect(isIncreasing).to.be.true
})
})
export * from './dummy' export * from './dummy'
export * from './constants' export * from './constants'
export * from './resolver' export * from './resolver'
export * from './utils'
/* External Imports */
import { BigNumber } from 'ethers'
/**
* Converts a string or buffer to a '0x'-prefixed hex string.
* @param buf String or buffer to convert.
* @returns '0x'-prefixed string.
*/
export const toHexString = (buf: Buffer | string): string => {
return '0x' + fromHexString(buf).toString('hex')
}
/**
* Converts a '0x'-prefixed string to a buffer.
* @param str '0x'-prefixed string to convert.
* @returns Hex buffer.
*/
export const fromHexString = (str: string | Buffer): Buffer => {
if (typeof str === 'string' && str.startsWith('0x')) {
return Buffer.from(str.slice(2), 'hex')
}
return Buffer.from(str)
}
export const toHexString32 = (
input: Buffer | string | number,
padRight = false
): string => {
if (typeof input === 'number') {
input = BigNumber.from(input).toHexString()
}
input = toHexString(input).slice(2)
return '0x' + (padRight ? input.padEnd(64, '0') : input.padStart(64, '0'))
}
export const getHexSlice = (
input: Buffer | string,
start: number,
length: number
): string => {
return toHexString(fromHexString(input).slice(start, start + length))
}
/**
* Generates a hex string of repeated bytes.
* @param byte Byte to repeat.
* @param len Number of times to repeat the byte.
* @return '0x'-prefixed hex string filled with the provided byte.
*/
export const makeHexString = (byte: string, len: number): string => {
return '0x' + byte.repeat(len)
}
/**
* Genereates an address with a repeated byte.
* @param byte Byte to repeat in the address.
* @return Address filled with the repeated byte.
*/
export const makeAddress = (byte: string): string => {
return makeHexString(byte, 20)
}
/**
* Removes '0x' from a hex string.
* @param str Hex string to remove '0x' from.
* @returns String without the '0x' prefix.
*/
export const remove0x = (str: string): string => {
if (str.startsWith('0x')) {
return str.slice(2)
} else {
return str
}
}
export const getEthTime = async (provider: any): Promise<number> => {
return (await provider.getBlock('latest')).timestamp
}
export const setEthTime = async (
provider: any,
time: number
): Promise<void> => {
await provider.send('evm_setNextBlockTimestamp', [time])
}
export const increaseEthTime = async (
provider: any,
amount: number
): Promise<void> => {
await setEthTime(provider, (await getEthTime(provider)) + amount)
await provider.send('evm_mine', [])
}
export const getBlockTime = async (
provider: any,
block: number
): Promise<number> => {
await provider.send('evm_mine', [])
return (await provider.getBlock(block)).timestamp
}
export const getNextBlockNumber = async (provider: any): Promise<number> => {
return (await provider.getBlock('latest')).number + 1
}
export * from './buffer-utils'
export * from './byte-utils'
export * from './eth-time'
...@@ -4,6 +4,19 @@ import { ...@@ -4,6 +4,19 @@ import {
TransactionResponse, TransactionResponse,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
export interface RollupInfo {
mode: 'sequencer' | 'verifier'
syncing: boolean
ethContext: {
blockNumber: number
timestamp: number
}
rollupContext: {
index: number
queueIndex: number
}
}
export enum QueueOrigin { export enum QueueOrigin {
Sequencer = 'sequencer', Sequencer = 'sequencer',
L1ToL2 = 'l1', L1ToL2 = 'l1',
......
...@@ -4,3 +4,4 @@ export * from './watcher' ...@@ -4,3 +4,4 @@ export * from './watcher'
export * from './base-service' export * from './base-service'
export * from './l2context' export * from './l2context'
export * from './events' export * from './events'
export * from './batches'
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