Skip to content
Merged
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
8 changes: 6 additions & 2 deletions packages/core-utils/src/app/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ export const Md5Hash = (preimage: string): string => {
* @param value Value to hash
* @returns the hash of the value.
*/
export const keccak256 = (value: string): string => {
export const keccak256 = (
value: string,
returnPrefixed: boolean = false
): string => {
const preimage = add0x(value)
return remove0x(ethers.utils.keccak256(preimage))
const hash = ethers.utils.keccak256(preimage)
return returnPrefixed ? hash : remove0x(hash)
}

/**
Expand Down
104 changes: 104 additions & 0 deletions packages/core-utils/src/app/ethereum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Contract, Wallet } from 'ethers'
import {
Provider,
TransactionRequest,
TransactionResponse,
} from 'ethers/providers'
import { getLogger } from './log'
import { Logger } from '../types'

const log: Logger = getLogger('core-utils/ethereum')

/**
* Populates and signs a transaction for the function call specified by the provided contract, function name, and args.
*
* @param contract The contract with a connected provider.
* @param functionName The function name to be invoked by the returned TransactionRequest
* @param functionArgs The arguments for the contract function to invoke
* @param wallet The wallet with which the transaction will be signed.
* @returns the constructed TransactionRequest.
*/
export const getSignedTransaction = async (
contract: Contract,
functionName: string,
functionArgs: any[],
wallet: Wallet
): Promise<string> => {
let tx: TransactionRequest
let nonce
;[tx, nonce] = await Promise.all([
populateFunctionCallTx(
contract,
functionName,
functionArgs,
wallet.address
),
wallet.getTransactionCount('pending'),
])

tx.nonce = nonce
return wallet.sign(tx)
}

/**
* Populates and returns a TransactionRequest for the function call specified by the provided contract, function name, and args.
*
* @param contract The contract with a connected provider.
* @param functionName The function name to be invoked by the returned TransactionRequest
* @param functionArgs The arguments for the contract function to invoke
* @param fromAddress The caller address for the tx
* @returns the constructed TransactionRequest.
*/
export const populateFunctionCallTx = async (
contract: Contract,
functionName: string,
functionArgs: any[],
fromAddress?: string
): Promise<TransactionRequest> => {
const data: string = contract.interface.functions[functionName].encode(
functionArgs
)
const tx: TransactionRequest = {
to: contract.address,
data,
}

const estimateTx = { ...tx }
if (!!fromAddress) {
estimateTx.from = fromAddress
}

log.debug(
`Getting gas limit and gas price for tx to ${
contract.address
} function ${functionName}, with args ${JSON.stringify(functionArgs)}`
)

let gasLimit
let gasPrice
;[gasLimit, gasPrice] = await Promise.all([
contract.provider.estimateGas(estimateTx),
contract.provider.getGasPrice(),
])

tx.gasLimit = gasLimit
tx.gasPrice = gasPrice

return tx
}

/**
* Determines whether or not the tx with the provided hash has been submitted to the chain.
* Note: This will return true if it has been submitted whether or not is has been mined.
*
* @param provider A provider to use for the fetch.
* @param txHash The transaction hash.
* @returns True if the tx with the provided hash has been submitted, false otherwise.
*/
export const isTxSubmitted = async (
provider: Provider,
txHash: string
): Promise<boolean> => {
const tx: TransactionResponse = await provider.getTransaction(txHash)
return !!tx
}
1 change: 1 addition & 0 deletions packages/core-utils/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './constants'
export * from './contract-deployment'
export * from './crypto'
export * from './equals'
export * from './ethereum'
export * from './log'
export * from './misc'
export * from './number'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
/* External Imports */
import {
getLogger,
getSignedTransaction,
isTxSubmitted,
keccak256,
logError,
numberToHexString,
remove0x,
ScheduledTask,
} from '@eth-optimism/core-utils'
import { Contract } from 'ethers'

import { Contract, Wallet } from 'ethers'
import { TransactionReceipt, TransactionResponse } from 'ethers/providers'
/* Internal Imports */
import {
TransactionBatchSubmission,
BatchSubmissionStatus,
L2DataService,
StateCommitmentBatchSubmission,
BatchSubmission,
TransactionBatchSubmission,
} from '../../../types/data'
import { TransactionReceipt, TransactionResponse } from 'ethers/providers'
import {
UnexpectedBatchStatus,
FutureRollupBatchNumberError,
FutureRollupBatchTimestampError,
RollupBatchBlockNumberTooOldError,
RollupBatchTimestampTooOldError,
RollupBatchSafetyQueueBlockNumberError,
RollupBatchSafetyQueueBlockTimestampError,
RollupBatchL1ToL2QueueBlockNumberError,
RollupBatchL1ToL2QueueBlockTimestampError,
RollupBatchOvmBlockNumberError,
RollupBatchOvmTimestampError,
RollupBatchSafetyQueueBlockNumberError,
RollupBatchSafetyQueueBlockTimestampError,
RollupBatchTimestampTooOldError,
UnexpectedBatchStatus,
} from '../../../types'

const log = getLogger('canonical-chain-batch-submitter')
Expand All @@ -42,6 +42,7 @@ export class CanonicalChainBatchSubmitter extends ScheduledTask {
private readonly canonicalTransactionChain: Contract,
private readonly l1ToL2QueueContract: Contract,
private readonly safetyQueueContract: Contract,
private readonly submitterWallet: Wallet,
periodMilliseconds = 10_000
) {
super(periodMilliseconds)
Expand Down Expand Up @@ -78,39 +79,88 @@ export class CanonicalChainBatchSubmitter extends ScheduledTask {
return false
}

if (batchSubmission.status !== BatchSubmissionStatus.QUEUED) {
if (
batchSubmission.status !== BatchSubmissionStatus.QUEUED &&
batchSubmission.status !== BatchSubmissionStatus.SUBMITTING
) {
const msg = `Received tx batch to send in ${
batchSubmission.status
} instead of ${
BatchSubmissionStatus.QUEUED
} instead of ${BatchSubmissionStatus.QUEUED} or ${
BatchSubmissionStatus.SUBMITTING
}. Batch Submission: ${JSON.stringify(batchSubmission)}.`
log.error(msg)
throw new UnexpectedBatchStatus(msg)
}

try {
const batchBlockNumber = await this.getBatchSubmissionBlockNumber()
if (await this.shouldSubmitBatch(batchSubmission)) {
try {
const batchBlockNumber = await this.getBatchSubmissionBlockNumber()

await this.validateBatchSubmission(batchSubmission, batchBlockNumber)
await this.validateBatchSubmission(batchSubmission, batchBlockNumber)

const txHash: string = await this.buildAndSendRollupBatchTransaction(
batchSubmission,
batchBlockNumber
)
if (!txHash) {
const txHash: string = await this.buildAndSendRollupBatchTransaction(
batchSubmission,
batchBlockNumber
)
if (!txHash) {
return false
}
batchSubmission.submissionTxHash = txHash
} catch (e) {
logError(log, `Error validating or submitting rollup tx batch`, e)
if (throwOnError) {
// this is only used by testing
throw e
}
return false
}
return this.waitForProofThatTransactionSucceeded(txHash, batchSubmission)
}

try {
return this.waitForProofThatTransactionSucceeded(
batchSubmission.submissionTxHash,
batchSubmission
)
} catch (e) {
logError(log, `Error validating or submitting rollup tx batch`, e)
if (throwOnError) {
// this is only used by testing
throw e
}
logError(
log,
`Error waiting for canonical tx chain batch ${batchSubmission.batchNumber} with hash ${batchSubmission.submissionTxHash} to succeed!`,
e
)
return false
}
}

protected async shouldSubmitBatch(batchSubmission): Promise<boolean> {
return (
batchSubmission.status === BatchSubmissionStatus.QUEUED ||
!(await isTxSubmitted(
this.canonicalTransactionChain.provider,
batchSubmission.submissionTxHash
))
)
}

protected async getSignedRollupBatchTx(
txsCalldata: string[],
timestamp: number,
batchBlockNumber: number,
startIndex: number
): Promise<string> {
return getSignedTransaction(
this.canonicalTransactionChain,
'appendSequencerBatch',
[txsCalldata, timestamp, batchBlockNumber, startIndex],
this.submitterWallet
)
}

protected async getBatchSubmissionBlockNumber(): Promise<number> {
// TODO: This will eventually be part of the output metadata from L2 tx outputs
// Need to update geth to have this functionality so this is a mock for now
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 ah yes we definitely need this. Got to double check that we have it represented as a ticket

return (await this.getL1BlockNumber()) - 10
}

/**
* Builds and sends a Rollup Batch transaction to L1, returning its tx hash.
*
Expand All @@ -131,16 +181,38 @@ export class CanonicalChainBatchSubmitter extends ScheduledTask {
log.debug(
`Submitting tx batch ${l2Batch.batchNumber} at start index ${l2Batch.startIndex} with ${l2Batch.transactions.length} transactions to canonical chain. Timestamp: ${timestamp}`
)
const txRes: TransactionResponse = await this.canonicalTransactionChain.appendSequencerBatch(

const signedTx: string = await this.getSignedRollupBatchTx(
txsCalldata,
timestamp,
batchBlockNumber,
l2Batch.startIndex
)

txHash = keccak256(signedTx, true)
await this.dataService.markTransactionBatchSubmittingToL1(
l2Batch.batchNumber,
txHash
)

log.debug(
`Marked tx ${txHash} for canonical tx batch ${l2Batch.batchNumber} as submitting.`
)

const txRes: TransactionResponse = await this.canonicalTransactionChain.provider.sendTransaction(
signedTx
)

log.debug(
`Tx batch ${l2Batch.batchNumber} appended with at least one confirmation! Tx Hash: ${txRes.hash}`
`Tx batch ${l2Batch.batchNumber} was sent to the canonical chain! Tx Hash: ${txRes.hash}`
)
txHash = txRes.hash

if (txHash !== txRes.hash) {
log.warn(
`Received tx hash not the same as calculated hash! Received: ${txRes.hash}, calculated: ${txHash}`
)
txHash = txRes.hash
}
} catch (e) {
logError(
log,
Expand Down Expand Up @@ -231,12 +303,6 @@ export class CanonicalChainBatchSubmitter extends ScheduledTask {
return true
}

protected async getBatchSubmissionBlockNumber(): Promise<number> {
// TODO: This will eventually be part of the output metadata from L2 tx outputs
// Need to update geth to have this functionality so this is a mock for now
return (await this.getL1BlockNumber()) - 10
}

private async validateBatchSubmission(
batchSubmission: TransactionBatchSubmission,
batchBlockNumber: number
Expand Down
Loading