From 760bab4426d22dfba17131db93df8bf34c614afb Mon Sep 17 00:00:00 2001 From: Will Meister Date: Fri, 12 Jun 2020 17:10:18 -0500 Subject: [PATCH 1/9] First stab at L1 -> L2 Transaction Batch Processor & submitter --- .../app/ethereum/ethereum-event-processor.ts | 10 +- packages/core-db/src/types/ethereum/event.ts | 3 + ...l2-transaction-batch-listener-submitter.ts | 123 ++++++++++++++ .../l1-to-l2-transaction-batch-processor.ts | 160 ++++++++++++++++++ packages/rollup-core/src/types/index.ts | 2 +- .../src/types/l1-to-l2-listener.ts | 20 +++ .../types/l1-to-l2-transaction-listener.ts | 8 - packages/rollup-core/src/types/types.ts | 18 ++ .../l1-to-l2-transaction-processor.spec.ts | 15 ++ .../test/l1-to-l2-transactions.spec.ts | 2 +- 10 files changed, 350 insertions(+), 11 deletions(-) create mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts create mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts create mode 100644 packages/rollup-core/src/types/l1-to-l2-listener.ts delete mode 100644 packages/rollup-core/src/types/l1-to-l2-transaction-listener.ts diff --git a/packages/core-db/src/app/ethereum/ethereum-event-processor.ts b/packages/core-db/src/app/ethereum/ethereum-event-processor.ts index 1247c984dc8f..947f2946fb98 100644 --- a/packages/core-db/src/app/ethereum/ethereum-event-processor.ts +++ b/packages/core-db/src/app/ethereum/ethereum-event-processor.ts @@ -154,7 +154,7 @@ export class EthereumEventProcessor { const logs: Log[] = await contract.provider.getLogs(filter) const events: EthereumEvent[] = logs.map((l) => { const logDesc: LogDescription = contract.interface.parseLog(l) - return EthereumEventProcessor.createEventFromLogDesc(logDesc, eventId) + return EthereumEventProcessor.createEventFromLogDesc(l, logDesc, eventId) }) for (const event of events) { @@ -222,11 +222,13 @@ export class EthereumEventProcessor { /** * Creates a local EthereumEvent from the provided Ethers LogDesc. * + * @param logObj The Log in question * @param logDesc The LogDesc in question * @param eventID The local event ID * @returns The local EthereumEvent */ private static createEventFromLogDesc( + logObj: Log, logDesc: LogDescription, eventID: string ): EthereumEvent { @@ -236,6 +238,9 @@ export class EthereumEventProcessor { name: logDesc.name, signature: logDesc.signature, values, + blockHash: logObj.blockHash, + blockNumber: logObj.blockNumber, + transactionHash: logObj.transactionHash, } } @@ -252,6 +257,9 @@ export class EthereumEventProcessor { name: event.event, signature: event.eventSignature, values, + blockHash: event.blockHash, + blockNumber: event.blockNumber, + transactionHash: event.transactionHash, } } diff --git a/packages/core-db/src/types/ethereum/event.ts b/packages/core-db/src/types/ethereum/event.ts index d6774df5f0d4..9ba8f1e93416 100644 --- a/packages/core-db/src/types/ethereum/event.ts +++ b/packages/core-db/src/types/ethereum/event.ts @@ -3,4 +3,7 @@ export interface EthereumEvent { name: string signature: string values: {} + blockHash: string + blockNumber: number + transactionHash: string } diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts new file mode 100644 index 000000000000..043a772a97d4 --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts @@ -0,0 +1,123 @@ +/* External Imports */ +import { + add0x, + getLogger, + isValidHexAddress, + logError, + remove0x, +} from '@eth-optimism/core-utils' + +import { JsonRpcProvider } from 'ethers/providers' +import { Wallet } from 'ethers' + +/* Internal Imports */ +import { + Address, + L1ToL2TransactionBatch, + L1ToL2TransactionBatchListener, +} from '../types' +import { CHAIN_ID, GAS_LIMIT } from './constants' + +const log = getLogger('l1-to-l2-tx-batch-listener-submitter') + +/** + * Handles L1 To L2 TransactionBatches, submitting them to the configured L2 node. + */ +export class L1ToL2TransactionBatchListenerSubmitter + implements L1ToL2TransactionBatchListener { + private readonly submissionToAddress: Address + private readonly submissionMethodId: string + + constructor( + private readonly wallet: Wallet, + private readonly provider: JsonRpcProvider, + submissionToAddress: Address, + submissionMethodId: string + ) { + if (!submissionMethodId || remove0x(submissionMethodId).length !== 8) { + throw Error( + `Invalid Transaction Batch submission method ID: ${remove0x( + submissionMethodId + )}. Expected 4 bytes (8 hex chars).` + ) + } + if (!isValidHexAddress(submissionToAddress)) { + throw Error( + `Invalid Transaction Batch submission to address: ${remove0x( + submissionToAddress + )}. Expected 20 bytes (40 hex chars).` + ) + } + this.submissionToAddress = add0x(submissionToAddress) + this.submissionMethodId = add0x(submissionMethodId) + } + + public async handleTransactionBatch( + transactionBatch: L1ToL2TransactionBatch + ): Promise { + log.debug( + `Received L1 to L2 Transaction Batch ${JSON.stringify(transactionBatch)}` + ) + + const signedTx = await this.getSignedBatchTransaction(transactionBatch) + + log.debug( + `sending signed tx for batch. Tx: ${JSON.stringify( + transactionBatch + )}. Signed: ${add0x(signedTx)}` + ) + const receipt = await this.provider.sendTransaction(signedTx) + + log.debug( + `L1 to L2 Transaction Batch Tx submitted. Tx hash: ${ + receipt.hash + }. Tx batch: ${JSON.stringify(transactionBatch)}` + ) + try { + const txReceipt = await this.provider.waitForTransaction(receipt.hash) + if (!txReceipt || !txReceipt.status) { + const msg = `Error processing L1 to L2 Transaction Batch. Tx batch: ${JSON.stringify( + transactionBatch + )}, Receipt: ${JSON.stringify(receipt)}` + log.error(msg) + throw new Error(msg) + } + } catch (e) { + logError( + log, + `Error submitting L1 to L2 Transaction Batch to L2 node. Tx Hash: ${ + receipt.hash + }, Tx: ${JSON.stringify(transactionBatch)}`, + e + ) + throw e + } + log.debug(`L1 to L2 Transaction applied to L2. Tx hash: ${receipt.hash}`) + } + + private async getSignedBatchTransaction( + transactionBatch: L1ToL2TransactionBatch + ): Promise { + const tx = { + nonce: transactionBatch.nonce, + gasPrice: 0, + gasLimit: GAS_LIMIT, + to: this.submissionToAddress, + value: 0, + data: await this.getL2TransactionBatchCalldata(transactionBatch.calldata), + chainId: CHAIN_ID, + } + + return this.wallet.sign(tx) + } + + private async getL2TransactionBatchCalldata( + l1Calldata: string + ): Promise { + const l1CalldataParams = + !l1Calldata || remove0x(l1Calldata).length < 8 + ? 0 + : remove0x(l1Calldata).substr(8) + return `${this.submissionMethodId}${l1CalldataParams}` + } +} diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts new file mode 100644 index 000000000000..c5227b833000 --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts @@ -0,0 +1,160 @@ +/* External Imports */ +import { + BaseQueuedPersistedProcessor, + DB, + EthereumEvent, + EthereumListener, +} from '@eth-optimism/core-db' +import { getLogger, logError, Logger } from '@eth-optimism/core-utils' + +/* Internal Imports */ +import { + L1ToL2Transaction, + L1ToL2TransactionBatch, + L1ToL2TransactionBatchListener, +} from '../types' +import { Provider, TransactionResponse } from 'ethers/providers' + +const log: Logger = getLogger('l1-to-l2-transition-batch-processor') + +export class L1ToL2TransactionBatchProcessor + extends BaseQueuedPersistedProcessor + implements EthereumListener { + public static readonly persistenceKey = 'L1ToL2TransitionBatchProcessor' + + private readonly provider: Provider + + public static async create( + db: DB, + l1ToL2EventId: string, + listeners: L1ToL2TransactionBatchListener[], + persistenceKey: string = L1ToL2TransactionBatchProcessor.persistenceKey + ): Promise { + const processor = new L1ToL2TransactionBatchProcessor( + db, + l1ToL2EventId, + listeners, + persistenceKey + ) + await processor.init() + return processor + } + + private constructor( + db: DB, + private readonly l1ToL2EventId: string, + private readonly listeners: L1ToL2TransactionBatchListener[], + persistenceKey: string = L1ToL2TransactionBatchProcessor.persistenceKey + ) { + super(db, persistenceKey) + } + + /** + * @inheritDoc + */ + public async handle(event: EthereumEvent): Promise { + if (event.eventID !== this.l1ToL2EventId || !event.values) { + log.debug( + `Received event of wrong ID or with incorrect values. Ignoring event: [${JSON.stringify( + event + )}]` + ) + return + } + + const calldata = await this.fetchCalldata(event.transactionHash) + + let transactions: L1ToL2Transaction[] = [] + try { + transactions = await this.parseTransactions(calldata) + } catch (e) { + // TODO: What do we do here? + logError( + log, + `Error parsing calldata for event ${JSON.stringify( + event + )}. Assuming this tx batch was malicious / invalid. Moving on.`, + e + ) + } + + const transactionBatch: L1ToL2TransactionBatch = { + nonce: event.values['_nonce'].toNumber(), + timestamp: event.values['_timestamp'].toNumber(), + transactions, + calldata, + } + + this.add(transactionBatch.nonce, transactionBatch) + } + + /** + * @inheritDoc + */ + public async onSyncCompleted(syncIdentifier?: string): Promise { + // no-op + } + + /** + * @inheritDoc + */ + protected async handleNextItem( + index: number, + item: L1ToL2TransactionBatch + ): Promise { + try { + await Promise.all( + this.listeners.map((x) => x.handleTransactionBatch(item)) + ) + await this.markProcessed(index) + } catch (e) { + this.logError( + `Error processing L1ToL2Transaction in at least one handler. Tx: ${JSON.stringify( + item + )}`, + e + ) + // All errors should be caught in the listeners, so this is fatal. + process.exit(1) + } + } + + /** + * @inheritDoc + */ + protected async serializeItem(item: L1ToL2TransactionBatch): Promise { + return Buffer.from(JSON.stringify(item), 'utf-8') + } + + /** + * @inheritDoc + */ + protected async deserializeItem( + itemBuffer: Buffer + ): Promise { + return JSON.parse(itemBuffer.toString('utf-8')) + } + + private async fetchCalldata(txHash: string): Promise { + let tx: TransactionResponse + try { + tx = await this.provider.getTransaction(txHash) + } catch (e) { + logError( + log, + `Error fetching tx hash ${txHash}. This should not ever happen.`, + e + ) + process.exit(1) + } + + return tx.data + } + + // TODO: This when a format is solidified + private async parseTransactions( + calldata: string + ): Promise { + return [] + } +} diff --git a/packages/rollup-core/src/types/index.ts b/packages/rollup-core/src/types/index.ts index 0309723ebb12..718855ef4234 100644 --- a/packages/rollup-core/src/types/index.ts +++ b/packages/rollup-core/src/types/index.ts @@ -1,5 +1,5 @@ export * from './errors' -export * from './l1-to-l2-transaction-listener' +export * from './l1-to-l2-listener' export * from './node-context' export * from './opcodes' export * from './state-machine' diff --git a/packages/rollup-core/src/types/l1-to-l2-listener.ts b/packages/rollup-core/src/types/l1-to-l2-listener.ts new file mode 100644 index 000000000000..407574c43344 --- /dev/null +++ b/packages/rollup-core/src/types/l1-to-l2-listener.ts @@ -0,0 +1,20 @@ +import { + L1ToL2StateCommitmentBatch, + L1ToL2Transaction, + L1ToL2TransactionBatch, +} from './types' + +/** + * Defines the event handler interface for L1-to-L2 Transactions. + */ +export interface L1ToL2TransactionListener { + handleL1ToL2Transaction(transaction: L1ToL2Transaction): Promise +} + +export interface L1ToL2TransactionBatchListener { + handleTransactionBatch(batch: L1ToL2TransactionBatch): Promise +} + +export interface L1ToL2StateCommitmentBatchHandler { + handleStateCommitmentBatch(batch: L1ToL2StateCommitmentBatch): Promise +} diff --git a/packages/rollup-core/src/types/l1-to-l2-transaction-listener.ts b/packages/rollup-core/src/types/l1-to-l2-transaction-listener.ts deleted file mode 100644 index 540adec6035e..000000000000 --- a/packages/rollup-core/src/types/l1-to-l2-transaction-listener.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { L1ToL2Transaction } from './types' - -/** - * Defines the event handler interface for L1-to-L2 Transactions. - */ -export interface L1ToL2TransactionListener { - handleL1ToL2Transaction(transaction: L1ToL2Transaction): Promise -} diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index 10b563db4161..a6c6e611334e 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -25,6 +25,24 @@ export interface L1ToL2Transaction { callData: string } +// TODO: Update when the format is known +export type StateCommitment = string +export interface RollupTransition { + nonce: number + transaction: L1ToL2Transaction + stateCommitment: StateCommitment +} +export interface L1ToL2TransactionBatch { + nonce: number + timestamp: number + transactions: L1ToL2Transaction[] + calldata: string +} +export interface L1ToL2StateCommitmentBatch { + nonce: number + stateCommitments: StateCommitment[] +} + /* Types */ export type Address = string export type StorageSlot = string diff --git a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts b/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts index 5c3a00851db7..9102ee4c0a6e 100644 --- a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts +++ b/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts @@ -58,6 +58,9 @@ describe('L1 to L2 Transaction Processor', () => { _target, _callData, }, + blockNumber: 1, + blockHash: keccak256(Buffer.from('block hash').toString('hex')), + transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), }) await sleep(100) @@ -95,6 +98,9 @@ describe('L1 to L2 Transaction Processor', () => { _target, _callData, }, + blockNumber: 1, + blockHash: keccak256(Buffer.from('block hash').toString('hex')), + transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), }) await l1ToL2TransactionProcessor.handle({ @@ -107,6 +113,9 @@ describe('L1 to L2 Transaction Processor', () => { _target: target2, _callData: callData2, }, + blockNumber: 1, + blockHash: keccak256(Buffer.from('block hash').toString('hex')), + transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), }) await sleep(100) @@ -161,6 +170,9 @@ describe('L1 to L2 Transaction Processor', () => { _target: target2, _callData: callData2, }, + blockNumber: 1, + blockHash: keccak256(Buffer.from('block hash').toString('hex')), + transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), }) await l1ToL2TransactionProcessor.handle({ @@ -173,6 +185,9 @@ describe('L1 to L2 Transaction Processor', () => { _target, _callData, }, + blockNumber: 1, + blockHash: keccak256(Buffer.from('block hash').toString('hex')), + transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), }) await sleep(100) diff --git a/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts b/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts index b77d71e065a6..e4fa6f08b48a 100644 --- a/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts +++ b/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts @@ -20,7 +20,7 @@ const log = getLogger('l1-to-l2-transactions', true) const storageKey: string = '0x' + '01'.repeat(32) const storageValue: string = '0x' + '22'.repeat(32) -describe('L1 To L2 Transaction Passing', () => { +describe.only('L1 To L2 Transaction Passing', () => { let wallet: Wallet let simpleStorage: Contract let provider: JsonRpcProvider From 85a76c6bff926449320e46a2124d12afae80d1c5 Mon Sep 17 00:00:00 2001 From: Will Meister Date: Mon, 15 Jun 2020 18:40:18 -0500 Subject: [PATCH 2/9] WIP adding transaction synchronizer --- packages/core-db/package.json | 2 +- .../app/ethereum/ethereum-block-processor.ts | 35 ++- .../app/ethereum/ethereum-event-processor.ts | 30 ++- .../app/ethereum/contracts/TestToken.json | 178 +++++++++----- .../test/app/ethereum/contracts/TestToken.sol | 5 + .../app/ethereum/contracts/waffle-config.json | 5 + .../ethereum/ethereum-event-processor.spec.ts | 228 +++++++++++------- packages/core-db/test/app/ethereum/utils.ts | 11 +- packages/core-utils/src/app/misc.ts | 4 +- .../l1-to-l2-transaction-batch-processor.ts | 213 ++++++++++++++++ packages/rollup-core/src/app/constants.ts | 1 + packages/rollup-core/src/app/index.ts | 2 + ...l2-transaction-batch-listener-submitter.ts | 14 ++ ...l1-to-l2-transaction-listener-submitter.ts | 2 +- .../src/app/l1-to-l2-transaction-processor.ts | 2 +- .../app/l1-to-l2-transaction-synchronizer.ts | 173 +++++++++++++ .../rollup-core/src/app/util/environment.ts | 10 +- packages/rollup-core/src/types/types.ts | 13 +- .../l1-to-l2-transaction-processor.spec.ts | 10 +- .../src/app/web3-rpc-handler.ts | 2 +- .../test/app/web-rpc-handler.spec.ts | 4 +- 21 files changed, 778 insertions(+), 166 deletions(-) create mode 100644 packages/core-db/test/app/ethereum/contracts/waffle-config.json create mode 100644 packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts create mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts diff --git a/packages/core-db/package.json b/packages/core-db/package.json index 1768cd7118d3..146bff5014fd 100644 --- a/packages/core-db/package.json +++ b/packages/core-db/package.json @@ -8,7 +8,7 @@ ], "scripts": { "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", - "build": "tsc -p .", + "build": "waffle test/app/ethereum/contracts/waffle-config.json && tsc -p .", "clean": "rimraf build/", "fix": "prettier --config ../../prettier-config.json --write 'index.ts' '{src,test}/**/*.ts'", "lint": "tslint --format stylish --project .", diff --git a/packages/core-db/src/app/ethereum/ethereum-block-processor.ts b/packages/core-db/src/app/ethereum/ethereum-block-processor.ts index c3d2fa208d0e..760d8950c4fe 100644 --- a/packages/core-db/src/app/ethereum/ethereum-block-processor.ts +++ b/packages/core-db/src/app/ethereum/ethereum-block-processor.ts @@ -5,6 +5,7 @@ import { Block, Provider } from 'ethers/providers' /* Internal Imports */ import { EthereumListener } from '../../types/ethereum' import { DB } from '../../types/db' +import { Transaction } from 'ethers/utils' const log = getLogger('ethereum-block-processor') const blockKey: Buffer = Buffer.from('latestBlock') @@ -22,7 +23,8 @@ export class EthereumBlockProcessor { constructor( private readonly db: DB, - private readonly earliestBlock: number = 0 + private readonly earliestBlock: number = 0, + private readonly confirmsUntilFinal: number = 1 ) { this.subscriptions = new Set>() this.currentBlockNumber = 0 @@ -49,6 +51,7 @@ export class EthereumBlockProcessor { provider.on('block', async (blockNumber) => { log.debug(`Block [${blockNumber}] was mined!`) + await this.fetchAndDisseminateBlock(provider, blockNumber) this.currentBlockNumber = blockNumber try { @@ -92,6 +95,36 @@ export class EthereumBlockProcessor { const block: Block = await provider.getBlock(blockNumber, true) log.debug(`Received block: [${JSON.stringify(block)}].`) + if ( + this.confirmsUntilFinal > 1 && + !!block.transactions && + !!block.transactions.length + ) { + log.debug( + `Waiting for ${this.confirmsUntilFinal} confirms before disseminating block ${blockNumber}` + ) + // TODO: What happens on re-org? I think we're stuck waiting on this confirmation that will never come forever. + try { + await provider.waitForTransaction( + (block.transactions[0] as any).hash, + this.confirmsUntilFinal + ) + } catch (e) { + logError( + log, + `Error waiting for ${this.confirmsUntilFinal} confirms on block ${blockNumber}`, + e + ) + // TODO: If this is not a re-org, this may require a restart or some other action. + // Monitor what actually happens and re-throw here if necessary. + return + } + + log.debug( + `Received ${this.confirmsUntilFinal} confirms for block ${blockNumber}` + ) + } + this.subscriptions.forEach((h) => { try { // purposefully ignore promise diff --git a/packages/core-db/src/app/ethereum/ethereum-event-processor.ts b/packages/core-db/src/app/ethereum/ethereum-event-processor.ts index 947f2946fb98..52e9cccf0e60 100644 --- a/packages/core-db/src/app/ethereum/ethereum-event-processor.ts +++ b/packages/core-db/src/app/ethereum/ethereum-event-processor.ts @@ -30,7 +30,8 @@ export class EthereumEventProcessor { constructor( private readonly db: DB, - private readonly earliestBlock: number = 0 + private readonly earliestBlock: number = 0, + private readonly confirmsUntilFinal: number = 1 ) { this.subscriptions = new Map>>() this.currentBlockNumber = 0 @@ -74,6 +75,33 @@ export class EthereumEventProcessor { log.debug(`Received live event: ${JSON.stringify(data)}`) const ethersEvent: ethers.Event = data[data.length - 1] const event: EthereumEvent = this.createEventFromEthersEvent(ethersEvent) + + if (this.confirmsUntilFinal > 1) { + log.debug( + `Waiting for ${ + this.confirmsUntilFinal + } confirms before disseminating event: ${JSON.stringify(data)}` + ) + // TODO: What happens on re-org? I think we're stuck waiting on this confirmation that will never come forever. + try { + await contract.provider.waitForTransaction( + event.transactionHash, + this.confirmsUntilFinal + ) + } catch (e) { + logError( + log, + `Error waiting for ${ + this.confirmsUntilFinal + } confirms on event ${JSON.stringify(data)}`, + e + ) + // TODO: If this is not a re-org, this may require a restart or some other action. + // Monitor what actually happens and re-throw here if necessary. + return + } + } + await this.handleEvent(event) try { await this.db.put( diff --git a/packages/core-db/test/app/ethereum/contracts/TestToken.json b/packages/core-db/test/app/ethereum/contracts/TestToken.json index 64f4311527b1..39c16fc1a959 100644 --- a/packages/core-db/test/app/ethereum/contracts/TestToken.json +++ b/packages/core-db/test/app/ethereum/contracts/TestToken.json @@ -1,23 +1,53 @@ { "abi": [ { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ + "inputs": [ { - "name": "", + "internalType": "uint256", + "name": "totalSupply", "type": "uint256" } ], "payable": false, - "stateMutability": "view", - "type": "function" + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" }, { "constant": true, "inputs": [ { + "internalType": "address", "name": "account", "type": "address" } @@ -25,6 +55,22 @@ "name": "balanceOf", "outputs": [ { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", "name": "", "type": "uint256" } @@ -37,14 +83,17 @@ "constant": false, "inputs": [ { + "internalType": "address", "name": "sender", "type": "address" }, { + "internalType": "address", "name": "recipient", "type": "address" }, { + "internalType": "uint256", "name": "amount", "type": "uint256" } @@ -55,9 +104,41 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes", + "name": "input", + "type": "bytes" + } + ], + "name": "updateMeaninglessHash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b506040516106333803806106338339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806001819055506001546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050610593806100a06000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80630bfd41fe1461005157806318160ddd1461010c57806370a082311461012a578063beabacc814610182575b600080fd5b61010a6004803603602081101561006757600080fd5b810190808035906020019064010000000081111561008457600080fd5b82018360208201111561009657600080fd5b803590602001918460018302840111640100000000831117156100b857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506101f0565b005b610114610201565b6040518082815260200191505060405180910390f35b61016c6004803603602081101561014057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020b565b6040518082815260200191505060405180910390f35b6101ee6004803603606081101561019857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610253565b005b808051906020012060028190555050565b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156102d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061053a6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561035f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806105176023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610413576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a72315820363e5257d891cee366bb1e28dea4b9fb6240774fdbff7c0cc7180a2671b3d04664736f6c63430005100032", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x633 CODESIZE SUB DUP1 PUSH2 0x633 DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x33 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP DUP1 PUSH1 0x1 DUP2 SWAP1 SSTORE POP PUSH1 0x1 SLOAD PUSH1 0x0 DUP1 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP POP PUSH2 0x593 DUP1 PUSH2 0xA0 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x4C JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xBFD41FE EQ PUSH2 0x51 JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0x10C JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x12A JUMPI DUP1 PUSH4 0xBEABACC8 EQ PUSH2 0x182 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x10A PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x67 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x84 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xB8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x1F0 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x114 PUSH2 0x201 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x16C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x140 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x20B JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1EE PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x198 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x253 JUMP JUMPDEST STOP JUMPDEST DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 PUSH1 0x2 DUP2 SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x2D9 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x53A PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x35F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x517 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD LT ISZERO PUSH2 0x413 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E73756666696369656E742073656E6465722062616C616E63650000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD SUB SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x9ED053BB818FF08B8353CD46F78DB1F0799F31C9E4458FDB425C10ECCD2EFC44 TIMESTAMP PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 POP POP POP JUMP INVALID GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220746F20746865207A65726F2061 PUSH5 0x6472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E736665722066726F6D20746865207A65726F KECCAK256 PUSH2 0x6464 PUSH19 0x657373A265627A7A72315820363E5257D891CE 0xE3 PUSH7 0xBB1E28DEA4B9FB PUSH3 0x40774F 0xDB SELFDESTRUCT PUSH29 0xCC7180A2671B3D04664736F6C63430005100032000000000000000000 ", + "sourceMap": "143:1128:0:-;;;397:127;8:9:-1;5:2;;;30:1;27;20:12;5:2;397:127:0;;;;;;;;;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;397:127:0;;;;;;;;;;;;;;;;466:11;451:12;:26;;;;507:12;;483:9;:21;493:10;483:21;;;;;;;;;;;;;;;:36;;;;397:127;143:1128;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b506004361061004c5760003560e01c80630bfd41fe1461005157806318160ddd1461010c57806370a082311461012a578063beabacc814610182575b600080fd5b61010a6004803603602081101561006757600080fd5b810190808035906020019064010000000081111561008457600080fd5b82018360208201111561009657600080fd5b803590602001918460018302840111640100000000831117156100b857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506101f0565b005b610114610201565b6040518082815260200191505060405180910390f35b61016c6004803603602081101561014057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020b565b6040518082815260200191505060405180910390f35b6101ee6004803603606081101561019857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610253565b005b808051906020012060028190555050565b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156102d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061053a6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561035f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806105176023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610413576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a72315820363e5257d891cee366bb1e28dea4b9fb6240774fdbff7c0cc7180a2671b3d04664736f6c63430005100032", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x4C JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xBFD41FE EQ PUSH2 0x51 JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0x10C JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x12A JUMPI DUP1 PUSH4 0xBEABACC8 EQ PUSH2 0x182 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x10A PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x67 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x84 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xB8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x1F0 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x114 PUSH2 0x201 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x16C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x140 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x20B JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1EE PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x198 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x253 JUMP JUMPDEST STOP JUMPDEST DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 PUSH1 0x2 DUP2 SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x2D9 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x53A PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x35F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x517 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD LT ISZERO PUSH2 0x413 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E73756666696369656E742073656E6465722062616C616E63650000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD SUB SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x9ED053BB818FF08B8353CD46F78DB1F0799F31C9E4458FDB425C10ECCD2EFC44 TIMESTAMP PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 POP POP POP JUMP INVALID GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220746F20746865207A65726F2061 PUSH5 0x6472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E736665722066726F6D20746865207A65726F KECCAK256 PUSH2 0x6464 PUSH19 0x657373A265627A7A72315820363E5257D891CE 0xE3 PUSH7 0xBB1E28DEA4B9FB PUSH3 0x40774F 0xDB SELFDESTRUCT PUSH29 0xCC7180A2671B3D04664736F6C63430005100032000000000000000000 ", + "sourceMap": "143:1128:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;143:1128:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1165:104;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;1165:104:0;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;1165:104:0;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;1165:104:0;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;1165:104:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;1165:104:0;;;;;;;;;;;;;;;:::i;:::-;;528:83;;;:::i;:::-;;;;;;;;;;;;;;;;;;;615:102;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;615:102:0;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;721:440;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;721:440:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1165:104;1258:5;1248:16;;;;;;1229;:35;;;;1165:104;:::o;528:83::-;572:7;594:12;;587:19;;528:83;:::o;615:102::-;672:7;694:9;:18;704:7;694:18;;;;;;;;;;;;;;;;687:25;;615:102;;;:::o;721:440::-;829:1;811:20;;:6;:20;;;;803:70;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;908:1;887:23;;:9;:23;;;;879:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;985:6;964:9;:17;974:6;964:17;;;;;;;;;;;;;;;;:27;;956:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1051:6;1030:9;:17;1040:6;1030:17;;;;;;;;;;;;;;;;:27;;;;;;;;;;;1087:6;1063:9;:20;1073:9;1063:20;;;;;;;;;;;;;;;;:30;;;;;;;;;;;1132:6;1121:9;1104:52;;1113:6;1104:52;;;1140:15;1104:52;;;;;;;;;;;;;;;;;;721:440;;;:::o" + } + }, + "interface": [ { "inputs": [ { + "internalType": "uint256", "name": "totalSupply", "type": "uint256" } @@ -71,50 +152,45 @@ "inputs": [ { "indexed": true, + "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, + "internalType": "address", "name": "to", "type": "address" }, { "indexed": true, + "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, + "internalType": "uint256", "name": "block", "type": "uint256" } ], "name": "Transfer", "type": "event" - } - ], - "evm": { - "bytecode": { - "linkReferences": {}, - "object": "608060405234801561001057600080fd5b5060405161055c38038061055c8339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806001819055506001546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550506104bc806100a06000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806318160ddd1461004657806370a0823114610064578063beabacc8146100bc575b600080fd5b61004e61012a565b6040518082815260200191505060405180910390f35b6100a66004803603602081101561007a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610134565b6040518082815260200191505060405180910390f35b610128600480360360608110156100d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061017c565b005b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610202576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806104636025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610288576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806104406023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561033c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a723058209e0509cf60775cda64e3464ccf0d32c16bcc3892f44a34a988a5caa645ac593264736f6c63430005090032", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x55C CODESIZE SUB DUP1 PUSH2 0x55C DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x33 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP DUP1 PUSH1 0x1 DUP2 SWAP1 SSTORE POP PUSH1 0x1 SLOAD PUSH1 0x0 DUP1 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP POP PUSH2 0x4BC DUP1 PUSH2 0xA0 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x18160DDD EQ PUSH2 0x46 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x64 JUMPI DUP1 PUSH4 0xBEABACC8 EQ PUSH2 0xBC JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x4E PUSH2 0x12A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xA6 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x7A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x134 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x128 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0xD2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x17C JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 PUSH1 0x1 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x202 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x463 PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x288 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x440 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD LT ISZERO PUSH2 0x33C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E73756666696369656E742073656E6465722062616C616E63650000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD SUB SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x9ED053BB818FF08B8353CD46F78DB1F0799F31C9E4458FDB425C10ECCD2EFC44 TIMESTAMP PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 POP POP POP JUMP INVALID GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220746F20746865207A65726F2061 PUSH5 0x6472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E736665722066726F6D20746865207A65726F KECCAK256 PUSH2 0x6464 PUSH19 0x657373A265627A7A723058209E0509CF60775C 0xda PUSH5 0xE3464CCF0D ORIGIN 0xc1 PUSH12 0xCC3892F44A34A988A5CAA645 0xac MSIZE ORIGIN PUSH5 0x736F6C6343 STOP SDIV MULMOD STOP ORIGIN ", - "sourceMap": "143:992:0:-;;;369:127;8:9:-1;5:2;;;30:1;27;20:12;5:2;369:127:0;;;;;;;;;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;369:127:0;;;;;;;;;;;;;;;;438:11;423:12;:26;;;;479:12;;455:9;:21;465:10;455:21;;;;;;;;;;;;;;;:36;;;;369:127;143:992;;;;;;" }, - "deployedBytecode": { - "linkReferences": {}, - "object": "608060405234801561001057600080fd5b50600436106100415760003560e01c806318160ddd1461004657806370a0823114610064578063beabacc8146100bc575b600080fd5b61004e61012a565b6040518082815260200191505060405180910390f35b6100a66004803603602081101561007a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610134565b6040518082815260200191505060405180910390f35b610128600480360360608110156100d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061017c565b005b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610202576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806104636025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610288576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806104406023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561033c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a723058209e0509cf60775cda64e3464ccf0d32c16bcc3892f44a34a988a5caa645ac593264736f6c63430005090032", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x18160DDD EQ PUSH2 0x46 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x64 JUMPI DUP1 PUSH4 0xBEABACC8 EQ PUSH2 0xBC JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x4E PUSH2 0x12A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xA6 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x7A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x134 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x128 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0xD2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x17C JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 PUSH1 0x1 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x202 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x463 PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x288 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x440 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD LT ISZERO PUSH2 0x33C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E73756666696369656E742073656E6465722062616C616E63650000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD SUB SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x9ED053BB818FF08B8353CD46F78DB1F0799F31C9E4458FDB425C10ECCD2EFC44 TIMESTAMP PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 POP POP POP JUMP INVALID GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220746F20746865207A65726F2061 PUSH5 0x6472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E736665722066726F6D20746865207A65726F KECCAK256 PUSH2 0x6464 PUSH19 0x657373A265627A7A723058209E0509CF60775C 0xda PUSH5 0xE3464CCF0D ORIGIN 0xc1 PUSH12 0xCC3892F44A34A988A5CAA645 0xac MSIZE ORIGIN PUSH5 0x736F6C6343 STOP SDIV MULMOD STOP ORIGIN ", - "sourceMap": "143:992:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;143:992:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;500:83;;;:::i;:::-;;;;;;;;;;;;;;;;;;;587:102;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;587:102:0;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;693:440;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;693:440:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;500:83;544:7;566:12;;559:19;;500:83;:::o;587:102::-;644:7;666:9;:18;676:7;666:18;;;;;;;;;;;;;;;;659:25;;587:102;;;:::o;693:440::-;801:1;783:20;;:6;:20;;;;775:70;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;880:1;859:23;;:9;:23;;;;851:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;957:6;936:9;:17;946:6;936:17;;;;;;;;;;;;;;;;:27;;928:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1023:6;1002:9;:17;1012:6;1002:17;;;;;;;;;;;;;;;;:27;;;;;;;;;;;1059:6;1035:9;:20;1045:9;1035:20;;;;;;;;;;;;;;;;:30;;;;;;;;;;;1104:6;1093:9;1076:52;;1085:6;1076:52;;;1112:15;1076:52;;;;;;;;;;;;;;;;;;693:440;;;:::o" - } - }, - "interface": [ { "constant": true, - "inputs": [], - "name": "totalSupply", + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", "outputs": [ { + "internalType": "uint256", "name": "", "type": "uint256" } @@ -125,15 +201,11 @@ }, { "constant": true, - "inputs": [ - { - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", + "inputs": [], + "name": "totalSupply", "outputs": [ { + "internalType": "uint256", "name": "", "type": "uint256" } @@ -146,14 +218,17 @@ "constant": false, "inputs": [ { + "internalType": "address", "name": "sender", "type": "address" }, { + "internalType": "address", "name": "recipient", "type": "address" }, { + "internalType": "uint256", "name": "amount", "type": "uint256" } @@ -165,43 +240,20 @@ "type": "function" }, { + "constant": false, "inputs": [ { - "name": "totalSupply", - "type": "uint256" + "internalType": "bytes", + "name": "input", + "type": "bytes" } ], + "name": "updateMeaninglessHash", + "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": true, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "block", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" + "type": "function" } ], - "bytecode": "608060405234801561001057600080fd5b5060405161055c38038061055c8339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806001819055506001546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550506104bc806100a06000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806318160ddd1461004657806370a0823114610064578063beabacc8146100bc575b600080fd5b61004e61012a565b6040518082815260200191505060405180910390f35b6100a66004803603602081101561007a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610134565b6040518082815260200191505060405180910390f35b610128600480360360608110156100d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061017c565b005b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610202576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806104636025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610288576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806104406023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561033c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a723058209e0509cf60775cda64e3464ccf0d32c16bcc3892f44a34a988a5caa645ac593264736f6c63430005090032" + "bytecode": "608060405234801561001057600080fd5b506040516106333803806106338339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806001819055506001546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050610593806100a06000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80630bfd41fe1461005157806318160ddd1461010c57806370a082311461012a578063beabacc814610182575b600080fd5b61010a6004803603602081101561006757600080fd5b810190808035906020019064010000000081111561008457600080fd5b82018360208201111561009657600080fd5b803590602001918460018302840111640100000000831117156100b857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506101f0565b005b610114610201565b6040518082815260200191505060405180910390f35b61016c6004803603602081101561014057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020b565b6040518082815260200191505060405180910390f35b6101ee6004803603606081101561019857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610253565b005b808051906020012060028190555050565b6000600154905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156102d9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061053a6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561035f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806105176023913960400191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610413576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f496e73756666696369656e742073656e6465722062616c616e6365000000000081525060200191505060405180910390fd5b806000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f9ed053bb818ff08b8353cd46f78db1f0799f31c9e4458fdb425c10eccd2efc44426040518082815260200191505060405180910390a450505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f2061646472657373a265627a7a72315820363e5257d891cee366bb1e28dea4b9fb6240774fdbff7c0cc7180a2671b3d04664736f6c63430005100032" } \ No newline at end of file diff --git a/packages/core-db/test/app/ethereum/contracts/TestToken.sol b/packages/core-db/test/app/ethereum/contracts/TestToken.sol index 676bb10782e0..8d8ecca4180c 100644 --- a/packages/core-db/test/app/ethereum/contracts/TestToken.sol +++ b/packages/core-db/test/app/ethereum/contracts/TestToken.sol @@ -4,6 +4,7 @@ pragma solidity ^0.5.1; contract TestToken { mapping (address => uint256) private _balances; uint256 private _totalSupply; + bytes32 _meaninglessHash; event Transfer( address indexed from, @@ -36,4 +37,8 @@ contract TestToken { _balances[recipient] += amount; emit Transfer(sender, recipient, amount, block.timestamp); } + + function updateMeaninglessHash(bytes memory input) public { + _meaninglessHash = keccak256(input); + } } \ No newline at end of file diff --git a/packages/core-db/test/app/ethereum/contracts/waffle-config.json b/packages/core-db/test/app/ethereum/contracts/waffle-config.json new file mode 100644 index 000000000000..c76a795e23cd --- /dev/null +++ b/packages/core-db/test/app/ethereum/contracts/waffle-config.json @@ -0,0 +1,5 @@ +{ + "sourcesPath": "./", + "targetPath": "./test/app/ethereum/contracts/", + "npmPath": "../../../../../../node_modules" +} diff --git a/packages/core-db/test/app/ethereum/ethereum-event-processor.spec.ts b/packages/core-db/test/app/ethereum/ethereum-event-processor.spec.ts index 1ad5dc5cfbcb..3b6ce7d5b0f9 100644 --- a/packages/core-db/test/app/ethereum/ethereum-event-processor.spec.ts +++ b/packages/core-db/test/app/ethereum/ethereum-event-processor.spec.ts @@ -26,101 +26,161 @@ describe('EthereumEvent Subscription', () => { let eventProcessor: EthereumEventProcessor let eventListener: TestListener - beforeEach(async () => { - provider = createMockProvider() - wallets = getWallets(provider) - ownerWallet = wallets[0] - recipientWallet = wallets[1] + describe('1 Confirm', () => { + beforeEach(async () => { + provider = createMockProvider() + wallets = getWallets(provider) + ownerWallet = wallets[0] + recipientWallet = wallets[1] - log.debug(`Connection info: ${JSON.stringify(provider.connection)}`) + log.debug(`Connection info: ${JSON.stringify(provider.connection)}`) - tokenContract = await deployTokenContract(ownerWallet, initialSupply) + tokenContract = await deployTokenContract(ownerWallet, initialSupply) - eventProcessor = new EthereumEventProcessor(newInMemoryDB()) - eventListener = new TestListener() - }) - - it('deploys correctly', async () => { - const ownerBalance = +(await tokenContract.balanceOf(ownerWallet.address)) - ownerBalance.should.equal(initialSupply) - }) + eventProcessor = new EthereumEventProcessor(newInMemoryDB()) + eventListener = new TestListener() + }) - it('processes new events', async () => { - await eventProcessor.subscribe( - tokenContract, - 'Transfer', - eventListener, - false - ) - - await tokenContract.transfer( - ownerWallet.address, - recipientWallet.address, - sendAmount - ) - - const events = await eventListener.waitForReceive() - events.length.should.equal(1) - const event: EthereumEvent = events[0] - event.values['from'].should.equal(ownerWallet.address) - event.values['to'].should.equal(recipientWallet.address) - event.values['amount'].toNumber().should.equal(sendAmount) - }).timeout(timeout) - - it('processes old events', async () => { - await tokenContract.transfer( - ownerWallet.address, - recipientWallet.address, - sendAmount - ) - - await tokenContract.provider.send('evm_mine', { - jsonrpc: '2.0', - id: 0, + it('deploys correctly', async () => { + const ownerBalance = +(await tokenContract.balanceOf(ownerWallet.address)) + ownerBalance.should.equal(initialSupply) }) - await eventProcessor.subscribe(tokenContract, 'Transfer', eventListener) - - const events = await eventListener.waitForSyncToComplete() - events.length.should.equal(1) - const event: EthereumEvent = events[0] - event.values['from'].should.equal(ownerWallet.address) - event.values['to'].should.equal(recipientWallet.address) - event.values['amount'].toNumber().should.equal(sendAmount) - }).timeout(timeout) - - it('processes new and old', async () => { - await tokenContract.transfer( - ownerWallet.address, - recipientWallet.address, - sendAmount - ) - - await tokenContract.provider.send('evm_mine', { - jsonrpc: '2.0', - id: 0, + it('processes new events', async () => { + await eventProcessor.subscribe( + tokenContract, + 'Transfer', + eventListener, + false + ) + + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount + ) + + const events = await eventListener.waitForReceive() + events.length.should.equal(1) + const event: EthereumEvent = events[0] + event.values['from'].should.equal(ownerWallet.address) + event.values['to'].should.equal(recipientWallet.address) + event.values['amount'].toNumber().should.equal(sendAmount) + }).timeout(timeout) + + it('processes old events', async () => { + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount + ) + + await tokenContract.provider.send('evm_mine', { + jsonrpc: '2.0', + id: 0, + }) + + await eventProcessor.subscribe(tokenContract, 'Transfer', eventListener) + + const events = await eventListener.waitForSyncToComplete() + events.length.should.equal(1) + const event: EthereumEvent = events[0] + event.values['from'].should.equal(ownerWallet.address) + event.values['to'].should.equal(recipientWallet.address) + event.values['amount'].toNumber().should.equal(sendAmount) + }).timeout(timeout) + + it('processes new and old', async () => { + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount + ) + + await tokenContract.provider.send('evm_mine', { + jsonrpc: '2.0', + id: 0, + }) + + await eventProcessor.subscribe(tokenContract, 'Transfer', eventListener) + + let events = await eventListener.waitForReceive() + events.length.should.equal(1) + const event1 = events[0] + + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount * 2 + ) + + events = await eventListener.waitForReceive() + log.debug( + `event 1: ${JSON.stringify(event1)}, rest: ${JSON.stringify(events)}` + ) + events.length.should.equal(1) + + !events[0].values['amount'] + .toNumber() + .should.not.equal(event1.values['amount'].toNumber()) }) + }) - await eventProcessor.subscribe(tokenContract, 'Transfer', eventListener) + describe('Additional Confirms', () => { + beforeEach(async () => { + provider = createMockProvider() + wallets = getWallets(provider) + ownerWallet = wallets[0] + recipientWallet = wallets[1] - let events = await eventListener.waitForReceive() - events.length.should.equal(1) - const event1 = events[0] + log.debug(`Connection info: ${JSON.stringify(provider.connection)}`) - await tokenContract.transfer( - ownerWallet.address, - recipientWallet.address, - sendAmount * 2 - ) + tokenContract = await deployTokenContract(ownerWallet, initialSupply) - events = await eventListener.waitForReceive() - log.debug( - `event 1: ${JSON.stringify(event1)}, rest: ${JSON.stringify(events)}` - ) - events.length.should.equal(1) + eventProcessor = new EthereumEventProcessor(newInMemoryDB(), 0, 2) + eventListener = new TestListener() + }) - !events[0].values['amount'] - .toNumber() - .should.not.equal(event1.values['amount'].toNumber()) + it('does not process new events without 2 confirms', async () => { + await eventProcessor.subscribe( + tokenContract, + 'Transfer', + eventListener, + false + ) + + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount + ) + + const events = await eventListener.waitForReceive(1, 10_000) + events.length.should.equal(0) + }).timeout(timeout) + + it('does process new events with 2 confirms', async () => { + await eventProcessor.subscribe( + tokenContract, + 'Transfer', + eventListener, + false + ) + + await tokenContract.transfer( + ownerWallet.address, + recipientWallet.address, + sendAmount + ) + + await tokenContract.updateMeaninglessHash('0x111111111111') + + const events = await eventListener.waitForReceive() + events.length.should.equal(1) + const event: EthereumEvent = events[0] + event.values['from'].should.equal(ownerWallet.address) + event.values['to'].should.equal(recipientWallet.address) + event.values['amount'].toNumber().should.equal(sendAmount) + }).timeout(timeout) }) }) diff --git a/packages/core-db/test/app/ethereum/utils.ts b/packages/core-db/test/app/ethereum/utils.ts index 9121d38dd5eb..aa3d9050ff68 100644 --- a/packages/core-db/test/app/ethereum/utils.ts +++ b/packages/core-db/test/app/ethereum/utils.ts @@ -35,8 +35,15 @@ export class TestListener implements EthereumListener { return this.received.splice(0) } - public async waitForReceive(num: number = 1): Promise { - while (this.received.length < num) { + public async waitForReceive( + num: number = 1, + timeoutMillis: number = -1 + ): Promise { + const startTime = new Date().getTime() + while ( + this.received.length < num && + (timeoutMillis < 0 || new Date().getTime() - startTime < timeoutMillis) + ) { await sleep(this.sleepMillis) } return this.getReceived() diff --git a/packages/core-utils/src/app/misc.ts b/packages/core-utils/src/app/misc.ts index d6828f9f06b3..d794663075cc 100644 --- a/packages/core-utils/src/app/misc.ts +++ b/packages/core-utils/src/app/misc.ts @@ -155,8 +155,8 @@ export const bnToHexString = (bn: BigNumber): string => { * @param number the JavaScript number to be converted. * @returns the JavaScript number as a string. */ -export const numberToHexString = (bn: number): string => { - return add0x(bn.toString(16)) +export const numberToHexString = (number: number): string => { + return add0x(number.toString(16)) } /** diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts new file mode 100644 index 000000000000..c5f9283f80cb --- /dev/null +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts @@ -0,0 +1,213 @@ +/* External Imports */ +import { + BaseDB, + DB, + EthereumEventProcessor, + getLevelInstance, + newInMemoryDB, +} from '@eth-optimism/core-db' +import { add0x, getLogger, logError } from '@eth-optimism/core-utils' +import { + Environment, + initializeL1Node, + initializeL2Node, + L1NodeContext, + L2NodeContext, + L1ToL2TransactionBatchProcessor, + L1ToL2TransactionEventName, + L1ToL2TransactionListener, + L1ToL2TransactionBatchListenerSubmitter, + CHAIN_ID, L1ToL2TransactionBatchEventName, L1ToL2TransactionBatchListener, +} from '@eth-optimism/rollup-core' + +import { JsonRpcProvider, Provider, Web3Provider } from 'ethers/providers' +import * as fs from 'fs' +import * as rimraf from 'rimraf' +import { Wallet } from 'ethers' +import { getWallets } from 'ethereum-waffle' + +const log = getLogger('l1-to-l2-transaction-batch-processor') + +export const runTest = async ( + l1Provider?: Provider, + l2Provider?: JsonRpcProvider +): Promise => { + return run(true, l1Provider, l2Provider) +} + +export const run = async ( + testFullNode: boolean = false, + l1Provider?: Provider, + l2Provider?: JsonRpcProvider +): Promise => { + initializeDBPaths(testFullNode) + + let l1NodeContext: L1NodeContext + log.info(`Attempting to connect to L1 Node.`) + try { + l1NodeContext = await initializeL1Node(true, l1Provider) + } catch (e) { + logError(log, 'Error connecting to L1 Node', e) + throw e + } + + let provider: JsonRpcProvider = l2Provider + if (!provider && !!Environment.l2NodeWeb3Url()) { + log.info(`Connecting to L2 web3 URL: ${Environment.l2NodeWeb3Url()}`) + provider = new JsonRpcProvider(Environment.l2NodeWeb3Url(), CHAIN_ID) + } + + const l2TransactionBatchListenerSubmitter = new L1ToL2TransactionBatchListenerSubmitter( + getWallet(provider), + provider, + Environment.transactionBatchSubmissionToAddress(), + Environment.transactionBatchSubmissionMethodId() + ) + + return getL1ToL2TransactionBatchProcessor( + testFullNode, + l1NodeContext, + l2TransactionBatchListenerSubmitter + ) +} + +/** + * Gets an L1ToL2TransactionBatchProcessor based on configuration and the provided arguments. + * + * Notably this will return undefined if configuration says not to connect to the L1 node. + * + * @param testFullnode Whether or not this is a test full node. + * @param l1NodeContext The L1 node context. + * @param listener The listener to listen to the processor. + * @returns The L1ToL2TransactionBatchProcessor or undefined. + */ +const getL1ToL2TransactionBatchProcessor = async ( + testFullnode: boolean, + l1NodeContext: L1NodeContext, + listener: L1ToL2TransactionBatchListener +): Promise => { + const db: DB = getDB(testFullnode) + const l1ToL2TransactionBatchProcessor: L1ToL2TransactionBatchProcessor = await L1ToL2TransactionBatchProcessor.create( + db, + EthereumEventProcessor.getEventID( + // TODO: Figure out config / deployment of Transaction Batch publisher contract + // it will likely not be the l1ToL2TransactionPasser contract. + l1NodeContext.l1ToL2TransactionPasser.address, + L1ToL2TransactionBatchEventName + ), + [listener] + ) + + const earliestBlock = Environment.l1EarliestBlock() + + const eventProcessor = new EthereumEventProcessor(db, earliestBlock) + await eventProcessor.subscribe( + // TODO: See above TODO + l1NodeContext.l1ToL2TransactionPasser, + L1ToL2TransactionBatchEventName, + l1ToL2TransactionBatchProcessor + ) + + return l1ToL2TransactionBatchProcessor +} + +/** + * Gets the appropriate db for this node to use based on whether or not this is run in test mode. + * + * @param isTestMode Whether or not it is test mode. + * @returns The constructed DB instance. + */ +const getDB = (isTestMode: boolean = false): DB => { + if (isTestMode) { + return newInMemoryDB() + } else { + if (!Environment.l1ToL2TxProcessorPersistentDbPath()) { + log.error( + `No L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH environment variable present. Please set one!` + ) + process.exit(1) + } + + return new BaseDB( + getLevelInstance(Environment.l1ToL2TxProcessorPersistentDbPath()) + ) + } +} + +/** + * Gets the wallet to use to interact with the L2 node. This may be configured via + * private key file specified through environment variables. If not it is assumed + * that a local test provider is being used, from which the wallet may be fetched. + * + * @param provider The provider with which the wallet will be associated. + * @returns The wallet to use with the L2 node. + */ +const getWallet = (provider: JsonRpcProvider): Wallet => { + let wallet: Wallet + if (!!Environment.l1ToL2TxProcessorPrivateKey()) { + wallet = new Wallet( + add0x(Environment.l1ToL2TxProcessorPrivateKey()), + provider + ) + log.info( + `Initialized L1-to-L2 Tx processor wallet from private key. Address: ${wallet.address}` + ) + } else { + wallet = getWallets(provider)[0] + log.info( + `Getting wallet from provider. First wallet private key: [${wallet.privateKey}` + ) + } + + if (!wallet) { + const msg: string = `Wallet not created! Specify the L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY environment variable to set one!` + log.error(msg) + throw Error(msg) + } else { + log.info(`L1-to-L2 Tx processor wallet created. Address: ${wallet.address}`) + } + + return wallet +} + +/** + * Initializes filesystem DB paths. This will also purge all data if the `CLEAR_DATA_KEY` has changed. + */ +const initializeDBPaths = (isTestMode: boolean) => { + if (isTestMode) { + return + } + + if (!fs.existsSync(Environment.l2RpcServerPersistentDbPath())) { + makeDataDirectory() + } else { + if (Environment.clearDataKey() && !fs.existsSync(getClearDataFilePath())) { + log.info(`Detected change in CLEAR_DATA_KEY. Purging data...`) + rimraf.sync(`${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}`) + log.info( + `L2 RPC Server data purged from '${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}'` + ) + makeDataDirectory() + } + } +} + +/** + * Makes the data directory for this full node and adds a clear data key file if it is configured to use one. + */ +const makeDataDirectory = () => { + fs.mkdirSync(Environment.l1ToL2TxProcessorPersistentDbPath(), { + recursive: true, + }) + if (Environment.clearDataKey()) { + fs.writeFileSync(getClearDataFilePath(), '') + } +} + +const getClearDataFilePath = () => { + return `${Environment.l1ToL2TxProcessorPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` +} + +if (typeof require !== 'undefined' && require.main === module) { + run() +} diff --git a/packages/rollup-core/src/app/constants.ts b/packages/rollup-core/src/app/constants.ts index fc173a6d64a3..9a27376866f9 100644 --- a/packages/rollup-core/src/app/constants.ts +++ b/packages/rollup-core/src/app/constants.ts @@ -2,6 +2,7 @@ import { ZERO_ADDRESS } from '@eth-optimism/core-utils' import { EVMOpcode, Opcode } from '../types' export const L1ToL2TransactionEventName = 'L1ToL2Transaction' +export const L1ToL2TransactionBatchEventName = 'NewTransactionBatchAdded' export const CREATOR_CONTRACT_ADDRESS = ZERO_ADDRESS export const GAS_LIMIT = 1_000_000_000 diff --git a/packages/rollup-core/src/app/index.ts b/packages/rollup-core/src/app/index.ts index 3cdc53dae2e3..f12190fb4cba 100644 --- a/packages/rollup-core/src/app/index.ts +++ b/packages/rollup-core/src/app/index.ts @@ -2,6 +2,8 @@ export * from './serialization' export * from './util' export * from './constants' +export * from './l1-to-l2-transaction-batch-listener-submitter' +export * from './l1-to-l2-transaction-batch-processor' export * from './l1-to-l2-transaction-listener-submitter' export * from './l1-to-l2-transaction-processor' export * from './utils' diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts index 043a772a97d4..83395c231325 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts @@ -95,6 +95,13 @@ export class L1ToL2TransactionBatchListenerSubmitter log.debug(`L1 to L2 Transaction applied to L2. Tx hash: ${receipt.hash}`) } + /** + * Builds the necessary Transaction to send the provided L1ToL2TransactionBatch + * to the L2 node. + * + * @param transactionBatch The transaction batch to wrap in a signed Transaction + * @return The signed Transaction. + */ private async getSignedBatchTransaction( transactionBatch: L1ToL2TransactionBatch ): Promise { @@ -111,6 +118,13 @@ export class L1ToL2TransactionBatchListenerSubmitter return this.wallet.sign(tx) } + /** + * Gets the calldata for the Transaction to send to the L2 node. Specifically, this + * swaps out the first 4 bytes (Method ID) of the calldata in favor of the Method ID + * of the L2 node method to call to submit the batch. + * + * @param l1Calldata The L1 Calldata for the Transaction Batch. + */ private async getL2TransactionBatchCalldata( l1Calldata: string ): Promise { diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts index a5ce5063927b..8206e5d381a0 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts @@ -72,7 +72,7 @@ export class L1ToL2TransactionListenerSubmitter gasLimit: GAS_LIMIT, to: transaction.target, value: 0, - data: add0x(transaction.callData), + data: add0x(transaction.calldata), chainId: CHAIN_ID, } diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts index f9670318fb7f..56e36d56f074 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts @@ -59,7 +59,7 @@ export class L1ToL2TransactionProcessor nonce: event.values['_nonce'].toNumber(), sender: event.values['_sender'], target: event.values['_target'], - callData: event.values['_callData'], + calldata: event.values['_callData'], } await this.add(transaction.nonce, transaction) diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts new file mode 100644 index 000000000000..182279eefa59 --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts @@ -0,0 +1,173 @@ +/* External Imports */ +import { + BaseQueuedPersistedProcessor, + DB, + EthereumListener, +} from '@eth-optimism/core-db' +import {getLogger, Logger, numberToHexString} from '@eth-optimism/core-utils' + +/* Internal Imports */ +import { + L1ToL2Transaction, + L1ToL2CalldataTransactionParser, TimestampedL1ToL2Transactions, +} from '../types' +import {Block, JsonRpcProvider, Provider, TransactionResponse} from 'ethers/providers' +import {Log} from 'ethers/providers/abstract-provider' +import {Wallet} from 'ethers' + +const log: Logger = getLogger('l1-to-l2-transition-synchronizer') + +// params: [timestampHex, transactionsArrayJSON, signedTransactionsArrayJSON] +const sendL1ToL2TransactionsMethod: string = "optimism_sendL1ToL2Transactions" + +export class L1ToL2TransactionSynchronizer + extends BaseQueuedPersistedProcessor + implements EthereumListener { + public static readonly persistenceKey = 'L1ToL2TransactionSynchronizer' + + private readonly topics: string[] + private readonly l2Provider: JsonRpcProvider + + /** + * Creates a L1ToL2TransactionSynchronizer that subscribes to blocks, processes all + * L1ToL2Transaction events, parses L1ToL2Transactions and submits them to L2. + * + * @param db The DB to use to persist the queue of L1ToL2Transaction[] objects. + * @param l1Provider The provider to use to connect to L1 to subscribe & fetch block / tx / log data. + * @param l2Wallet The L2 wallet to use to submit transactions ** ASSUMED TO BE CONNECTED TO THE L2 JSON RPC PROVIDER ** + * @param logTopicToCalldataParserMap The map of Log Topics -> L1ToL2CalldataTransactionParser to indicate + * which logs should be fetched and parsed as well as how. + * @param persistenceKey The persistence key to use for this instance within the provided DB. + */ + public static async create( + db: DB, + l1Provider: Provider, + l2Wallet: Wallet, + logTopicToCalldataParserMap: Map, + persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey + ): Promise { + const processor = new L1ToL2TransactionSynchronizer( + db, + l1Provider, + l2Wallet, + logTopicToCalldataParserMap, + persistenceKey + ) + await processor.init() + return processor + } + + private constructor( + db: DB, + private readonly l1Provider: Provider, + private readonly l2Wallet: Wallet, + private readonly logTopicToCalldataParserMap: Map, + persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey + ) { + super(db, persistenceKey) + this.topics = Array.from(this.logTopicToCalldataParserMap.keys()) + this.l2Provider = (l2Wallet.provider as JsonRpcProvider) + } + + /** + * @inheritDoc + */ + public async handle(block: Block): Promise { + log.debug(`Received block ${block.number}. Searching for any contained L1toL2Transactions.`) + + const logs: Log[] = await this.l1Provider.getLogs({blockHash: block.hash, topics: this.topics}) + + const l1ToL2TransactionArrays: L1ToL2Transaction[][] = await Promise.all(logs.map(l => this.getTransactionsFromLog(l))) + const transactions: L1ToL2Transaction[] = l1ToL2TransactionArrays.reduce((res, curr) => [...res, ...curr], []) + + if (!transactions.length) { + log.debug(`There were no L1toL2Transactions in block ${block.number}.`) + } else { + log.debug(`Parsed L1ToL2Transactions from block ${block.number}: ${JSON.stringify(transactions)}`) + } + + this.add(block.number, { + timestamp: block.timestamp, + transactions: transactions + }) + } + + /** + * @inheritDoc + */ + public async onSyncCompleted(syncIdentifier?: string): Promise { + // TODO: Turn off processing of CannonicalTransactionChainBatch events here + } + + /** + * @inheritDoc + */ + protected async handleNextItem( + blockNumber: number, + timestampedTransactions: TimestampedL1ToL2Transactions + ): Promise { + try { + if (!timestampedTransactions.transactions || !timestampedTransactions.transactions.length) { + log.debug(`Moving past empty block ${blockNumber}.`) + await this.markProcessed(blockNumber) + return + } + + const timestamp: string = numberToHexString(timestampedTransactions.timestamp) + const txs = JSON.stringify(timestampedTransactions.transactions.map(x => { + return { + nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', + sender: x.sender, + calldata: x.calldata + } + })) + const signedTxsArray: string = await this.l2Wallet.signMessage(txs) + await this.l2Provider.send(sendL1ToL2TransactionsMethod, [timestamp, txs, signedTxsArray]) + + await this.markProcessed(blockNumber) + } catch (e) { + this.logError( + `Error processing L1ToL2Transactions. Txs: ${JSON.stringify( + timestampedTransactions + )}`, + e + ) + // Can't properly sync from L1 to L2, and need to do so in order. This is fatal. + process.exit(1) + } + } + + /** + * @inheritDoc + */ + protected async serializeItem(item: TimestampedL1ToL2Transactions): Promise { + return Buffer.from(JSON.stringify(item), 'utf-8') + } + + /** + * @inheritDoc + */ + protected async deserializeItem( + itemBuffer: Buffer + ): Promise { + return JSON.parse(itemBuffer.toString('utf-8')) + } + + private async getTransactionsFromLog(l: Log): Promise> { + const matchedTopics: string[] = l.topics.filter(x => this.topics.indexOf(x) >= 0) + if (matchedTopics.length === 0) { + log.error(`Received log with topics: ${l.topics.join(',')} for subscription to topics: ${this.topics.join(',')}. Transaction: ${l.transactionHash}`) + return [] + } + + const transaction: TransactionResponse = await this.l1Provider.getTransaction(l.transactionHash) + + const parsedTransactions: L1ToL2Transaction[] = [] + for (const topic of matchedTopics) { + const transactions = await this.logTopicToCalldataParserMap.get(topic).parseTransactions(transaction) + parsedTransactions.push(...transactions) + } + + return parsedTransactions + } +} diff --git a/packages/rollup-core/src/app/util/environment.ts b/packages/rollup-core/src/app/util/environment.ts index 252580c2a202..7399360bf69a 100644 --- a/packages/rollup-core/src/app/util/environment.ts +++ b/packages/rollup-core/src/app/util/environment.ts @@ -187,8 +187,16 @@ export class Environment { public static l1ToL2TxProcessorPersistentDbPath(defaultValue?: string) { return process.env.L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH || defaultValue } - public static l1ToL2TxProcessorPrivateKey(defaultValue?: string) { return process.env.L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY || defaultValue } + + // L1 to L2 Tx Batch Processor Config + public static transactionBatchSubmissionToAddress(defaultValue?: string) { + return process.env.TRANSACTION_BATCH_SUBMISSION_TO_ADDRESS || defaultValue + } + public static transactionBatchSubmissionMethodId(defaultValue?: string) { + return process.env.TRANSACTION_BATCH_SUBMISSION_METHOD_ID || defaultValue + } + } diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index a6c6e611334e..3cfef820c3b4 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -1,6 +1,8 @@ /* External Imports */ import { BigNumber } from '@eth-optimism/core-utils' +import {TransactionResponse} from 'ethers/providers/abstract-provider' + // TODO: Probably not necessary? // Maybe just a map from token -> contract slot index (e.g. {ETH: 1, BAT: 2, REP: 3})? export type TokenType = number @@ -22,7 +24,16 @@ export interface L1ToL2Transaction { nonce: number sender: Address target: Address - callData: string + calldata: string +} + +export interface TimestampedL1ToL2Transactions { + timestamp: number + transactions: L1ToL2Transaction[] +} + +export interface L1ToL2CalldataTransactionParser { + parseTransactions(transaction: TransactionResponse): Promise } // TODO: Update when the format is known diff --git a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts b/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts index 9102ee4c0a6e..99ed7b87e444 100644 --- a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts +++ b/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts @@ -81,7 +81,7 @@ describe('L1 to L2 Transaction Processor', () => { _target, `Incorrect target!` ) - listener.receivedTransactions[0].callData.should.equal( + listener.receivedTransactions[0].calldata.should.equal( _callData, `Incorrect calldata!` ) @@ -136,7 +136,7 @@ describe('L1 to L2 Transaction Processor', () => { _target, `Incorrect target!` ) - listener.receivedTransactions[0].callData.should.equal( + listener.receivedTransactions[0].calldata.should.equal( _callData, `Incorrect calldata!` ) @@ -153,7 +153,7 @@ describe('L1 to L2 Transaction Processor', () => { target2, `Incorrect target 2!` ) - listener.receivedTransactions[1].callData.should.equal( + listener.receivedTransactions[1].calldata.should.equal( callData2, `Incorrect calldata 2!` ) @@ -208,7 +208,7 @@ describe('L1 to L2 Transaction Processor', () => { _target, `Incorrect target!` ) - listener.receivedTransactions[0].callData.should.equal( + listener.receivedTransactions[0].calldata.should.equal( _callData, `Incorrect calldata!` ) @@ -225,7 +225,7 @@ describe('L1 to L2 Transaction Processor', () => { target2, `Incorrect target 2!` ) - listener.receivedTransactions[1].callData.should.equal( + listener.receivedTransactions[1].calldata.should.equal( callData2, `Incorrect calldata 2!` ) diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index dc63fb857d53..c579f42fb78c 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -826,7 +826,7 @@ export class DefaultWeb3Handler this.getTimestamp(), 0, transaction.target, - transaction.callData, + transaction.calldata, ZERO_ADDRESS, transaction.sender, false, diff --git a/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts b/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts index c4dee0ec6219..5be95de494f1 100644 --- a/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts +++ b/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts @@ -633,7 +633,7 @@ describe('Web3Handler', () => { ) await web3Handler.handleL1ToL2Transaction({ nonce: 0, - callData, + calldata: callData, sender: wallet.address, target: simpleStorage.address, }) @@ -653,7 +653,7 @@ describe('Web3Handler', () => { ) await web3Handler.handleL1ToL2Transaction({ nonce: 0, - callData, + calldata: callData, sender: wallet.address, target: simpleStorage.address, }) From 77d489f3edc7069250217f5c1b7403d30d98ee6a Mon Sep 17 00:00:00 2001 From: Will Meister Date: Tue, 16 Jun 2020 08:51:39 -0500 Subject: [PATCH 3/9] Adding logic to State Synchronizer to filter logs by contract address --- .../app/l1-to-l2-transaction-synchronizer.ts | 26 +++++++++++------ packages/rollup-core/src/app/utils.ts | 29 +++++++++++++++---- packages/rollup-core/src/types/types.ts | 7 +++-- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts index 182279eefa59..2a1622d829a2 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts @@ -4,16 +4,18 @@ import { DB, EthereumListener, } from '@eth-optimism/core-db' -import {getLogger, Logger, numberToHexString} from '@eth-optimism/core-utils' +import {getLogger, Logger, numberToHexString, remove0x} from '@eth-optimism/core-utils' /* Internal Imports */ import { L1ToL2Transaction, - L1ToL2CalldataTransactionParser, TimestampedL1ToL2Transactions, + TimestampedL1ToL2Transactions, + L1ToL2TransactionLogParserContext, } from '../types' import {Block, JsonRpcProvider, Provider, TransactionResponse} from 'ethers/providers' import {Log} from 'ethers/providers/abstract-provider' import {Wallet} from 'ethers' +import {addressesAreEqual} from './utils' const log: Logger = getLogger('l1-to-l2-transition-synchronizer') @@ -26,6 +28,7 @@ export class L1ToL2TransactionSynchronizer public static readonly persistenceKey = 'L1ToL2TransactionSynchronizer' private readonly topics: string[] + private readonly topicMap: Map private readonly l2Provider: JsonRpcProvider /** @@ -35,22 +38,22 @@ export class L1ToL2TransactionSynchronizer * @param db The DB to use to persist the queue of L1ToL2Transaction[] objects. * @param l1Provider The provider to use to connect to L1 to subscribe & fetch block / tx / log data. * @param l2Wallet The L2 wallet to use to submit transactions ** ASSUMED TO BE CONNECTED TO THE L2 JSON RPC PROVIDER ** - * @param logTopicToCalldataParserMap The map of Log Topics -> L1ToL2CalldataTransactionParser to indicate - * which logs should be fetched and parsed as well as how. + * @param logContexts The collection of L1ToL2TransactionLogParserContext that uniquely identify the log event and + * provide the ability to create L2 transactions from the L1 transaction that emitted it. * @param persistenceKey The persistence key to use for this instance within the provided DB. */ public static async create( db: DB, l1Provider: Provider, l2Wallet: Wallet, - logTopicToCalldataParserMap: Map, + logContexts: L1ToL2TransactionLogParserContext[], persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey ): Promise { const processor = new L1ToL2TransactionSynchronizer( db, l1Provider, l2Wallet, - logTopicToCalldataParserMap, + logContexts, persistenceKey ) await processor.init() @@ -61,11 +64,12 @@ export class L1ToL2TransactionSynchronizer db: DB, private readonly l1Provider: Provider, private readonly l2Wallet: Wallet, - private readonly logTopicToCalldataParserMap: Map, + logContexts: L1ToL2TransactionLogParserContext[], persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey ) { super(db, persistenceKey) - this.topics = Array.from(this.logTopicToCalldataParserMap.keys()) + this.topicMap = new Map(logContexts.map(x => [x.topic, x])) + this.topics = Array.from(this.topicMap.keys()) this.l2Provider = (l2Wallet.provider as JsonRpcProvider) } @@ -164,7 +168,11 @@ export class L1ToL2TransactionSynchronizer const parsedTransactions: L1ToL2Transaction[] = [] for (const topic of matchedTopics) { - const transactions = await this.logTopicToCalldataParserMap.get(topic).parseTransactions(transaction) + const context = this.topicMap.get(topic) + if (!addressesAreEqual(l.address,context.contractAddress)) { + continue + } + const transactions = await context.parseL2Transactions(transaction) parsedTransactions.push(...transactions) } diff --git a/packages/rollup-core/src/app/utils.ts b/packages/rollup-core/src/app/utils.ts index dbfbdda0ac5b..22fa99bffcfd 100644 --- a/packages/rollup-core/src/app/utils.ts +++ b/packages/rollup-core/src/app/utils.ts @@ -1,10 +1,10 @@ /* External Imports */ -import { bufToHexString } from '@eth-optimism/core-utils' +import {bufToHexString, remove0x} from '@eth-optimism/core-utils' import { Contract, ContractFactory, Wallet } from 'ethers' /* Internal Imports */ -import { EVMBytecode, EVMOpcodeAndBytes, Opcode } from '../types' +import {Address, EVMBytecode, EVMOpcodeAndBytes, Opcode} from '../types' /** * Creates an unsigned transaction and returns its calldata. @@ -132,7 +132,7 @@ export const getPCOfEVMBytecodeIndex = ( return pc } -export function getWallets(httpProvider) { +export const getWallets = (httpProvider) => { const walletsToReturn = [] for (let i = 0; i < 9; i++) { const privateKey = '0x' + ('5' + i).repeat(32) @@ -142,12 +142,12 @@ export function getWallets(httpProvider) { return walletsToReturn } -export async function deployContract( +export const deployContract = async ( wallet, contractJSON, args, overrideOptions = {} -) { +) => { const factory = new ContractFactory( contractJSON.abi, contractJSON.bytecode || contractJSON.evm.bytecode, @@ -164,6 +164,23 @@ export async function deployContract( * * @returns The seconds since epoch. */ -export function getCurrentTime(): number { +export const getCurrentTime = (): number => { return Math.round(new Date().getTime() / 1000) } + +/** + * Returns whether or not the provided addresses are equal, ignoring case and prefix. + * + * @param one The first address. + * @param two The second address + */ +export const addressesAreEqual = (one: Address, two: Address): boolean => { + if (!one && !two) { + return true + } + if (!one || !two) { + return false + } + + return remove0x(one).toLowerCase() === remove0x(two).toLowerCase() +} diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index 3cfef820c3b4..dd779781e721 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -32,8 +32,11 @@ export interface TimestampedL1ToL2Transactions { transactions: L1ToL2Transaction[] } -export interface L1ToL2CalldataTransactionParser { - parseTransactions(transaction: TransactionResponse): Promise +export type L1ToL2TransactionParser = (transaction: TransactionResponse) => Promise +export interface L1ToL2TransactionLogParserContext { + topic: string + contractAddress: Address + parseL2Transactions: L1ToL2TransactionParser } // TODO: Update when the format is known From 42250b4d16b36786bad43859e18121b7f0aa60d3 Mon Sep 17 00:00:00 2001 From: Will Meister Date: Tue, 16 Jun 2020 08:52:59 -0500 Subject: [PATCH 4/9] fixing linting --- .../l1-to-l2-transaction-batch-processor.ts | 4 +- .../app/l1-to-l2-transaction-synchronizer.ts | 109 +++++++++++++----- .../rollup-core/src/app/util/environment.ts | 1 - packages/rollup-core/src/app/utils.ts | 4 +- packages/rollup-core/src/types/types.ts | 6 +- 5 files changed, 88 insertions(+), 36 deletions(-) diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts index c5f9283f80cb..3ef92b126313 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts @@ -17,7 +17,9 @@ import { L1ToL2TransactionEventName, L1ToL2TransactionListener, L1ToL2TransactionBatchListenerSubmitter, - CHAIN_ID, L1ToL2TransactionBatchEventName, L1ToL2TransactionBatchListener, + CHAIN_ID, + L1ToL2TransactionBatchEventName, + L1ToL2TransactionBatchListener, } from '@eth-optimism/rollup-core' import { JsonRpcProvider, Provider, Web3Provider } from 'ethers/providers' diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts index 2a1622d829a2..383eef51bdc4 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts @@ -4,7 +4,12 @@ import { DB, EthereumListener, } from '@eth-optimism/core-db' -import {getLogger, Logger, numberToHexString, remove0x} from '@eth-optimism/core-utils' +import { + getLogger, + Logger, + numberToHexString, + remove0x, +} from '@eth-optimism/core-utils' /* Internal Imports */ import { @@ -12,15 +17,20 @@ import { TimestampedL1ToL2Transactions, L1ToL2TransactionLogParserContext, } from '../types' -import {Block, JsonRpcProvider, Provider, TransactionResponse} from 'ethers/providers' -import {Log} from 'ethers/providers/abstract-provider' -import {Wallet} from 'ethers' -import {addressesAreEqual} from './utils' +import { + Block, + JsonRpcProvider, + Provider, + TransactionResponse, +} from 'ethers/providers' +import { Log } from 'ethers/providers/abstract-provider' +import { Wallet } from 'ethers' +import { addressesAreEqual } from './utils' const log: Logger = getLogger('l1-to-l2-transition-synchronizer') // params: [timestampHex, transactionsArrayJSON, signedTransactionsArrayJSON] -const sendL1ToL2TransactionsMethod: string = "optimism_sendL1ToL2Transactions" +const sendL1ToL2TransactionsMethod: string = 'optimism_sendL1ToL2Transactions' export class L1ToL2TransactionSynchronizer extends BaseQueuedPersistedProcessor @@ -68,31 +78,47 @@ export class L1ToL2TransactionSynchronizer persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey ) { super(db, persistenceKey) - this.topicMap = new Map(logContexts.map(x => [x.topic, x])) + this.topicMap = new Map( + logContexts.map((x) => [x.topic, x]) + ) this.topics = Array.from(this.topicMap.keys()) - this.l2Provider = (l2Wallet.provider as JsonRpcProvider) + this.l2Provider = l2Wallet.provider as JsonRpcProvider } /** * @inheritDoc */ public async handle(block: Block): Promise { - log.debug(`Received block ${block.number}. Searching for any contained L1toL2Transactions.`) + log.debug( + `Received block ${block.number}. Searching for any contained L1toL2Transactions.` + ) - const logs: Log[] = await this.l1Provider.getLogs({blockHash: block.hash, topics: this.topics}) + const logs: Log[] = await this.l1Provider.getLogs({ + blockHash: block.hash, + topics: this.topics, + }) - const l1ToL2TransactionArrays: L1ToL2Transaction[][] = await Promise.all(logs.map(l => this.getTransactionsFromLog(l))) - const transactions: L1ToL2Transaction[] = l1ToL2TransactionArrays.reduce((res, curr) => [...res, ...curr], []) + const l1ToL2TransactionArrays: L1ToL2Transaction[][] = await Promise.all( + logs.map((l) => this.getTransactionsFromLog(l)) + ) + const transactions: L1ToL2Transaction[] = l1ToL2TransactionArrays.reduce( + (res, curr) => [...res, ...curr], + [] + ) if (!transactions.length) { log.debug(`There were no L1toL2Transactions in block ${block.number}.`) } else { - log.debug(`Parsed L1ToL2Transactions from block ${block.number}: ${JSON.stringify(transactions)}`) + log.debug( + `Parsed L1ToL2Transactions from block ${block.number}: ${JSON.stringify( + transactions + )}` + ) } this.add(block.number, { timestamp: block.timestamp, - transactions: transactions + transactions, }) } @@ -111,22 +137,33 @@ export class L1ToL2TransactionSynchronizer timestampedTransactions: TimestampedL1ToL2Transactions ): Promise { try { - if (!timestampedTransactions.transactions || !timestampedTransactions.transactions.length) { + if ( + !timestampedTransactions.transactions || + !timestampedTransactions.transactions.length + ) { log.debug(`Moving past empty block ${blockNumber}.`) await this.markProcessed(blockNumber) return } - const timestamp: string = numberToHexString(timestampedTransactions.timestamp) - const txs = JSON.stringify(timestampedTransactions.transactions.map(x => { - return { - nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', - sender: x.sender, - calldata: x.calldata - } - })) + const timestamp: string = numberToHexString( + timestampedTransactions.timestamp + ) + const txs = JSON.stringify( + timestampedTransactions.transactions.map((x) => { + return { + nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', + sender: x.sender, + calldata: x.calldata, + } + }) + ) const signedTxsArray: string = await this.l2Wallet.signMessage(txs) - await this.l2Provider.send(sendL1ToL2TransactionsMethod, [timestamp, txs, signedTxsArray]) + await this.l2Provider.send(sendL1ToL2TransactionsMethod, [ + timestamp, + txs, + signedTxsArray, + ]) await this.markProcessed(blockNumber) } catch (e) { @@ -144,7 +181,9 @@ export class L1ToL2TransactionSynchronizer /** * @inheritDoc */ - protected async serializeItem(item: TimestampedL1ToL2Transactions): Promise { + protected async serializeItem( + item: TimestampedL1ToL2Transactions + ): Promise { return Buffer.from(JSON.stringify(item), 'utf-8') } @@ -157,19 +196,29 @@ export class L1ToL2TransactionSynchronizer return JSON.parse(itemBuffer.toString('utf-8')) } - private async getTransactionsFromLog(l: Log): Promise> { - const matchedTopics: string[] = l.topics.filter(x => this.topics.indexOf(x) >= 0) + private async getTransactionsFromLog(l: Log): Promise { + const matchedTopics: string[] = l.topics.filter( + (x) => this.topics.indexOf(x) >= 0 + ) if (matchedTopics.length === 0) { - log.error(`Received log with topics: ${l.topics.join(',')} for subscription to topics: ${this.topics.join(',')}. Transaction: ${l.transactionHash}`) + log.error( + `Received log with topics: ${l.topics.join( + ',' + )} for subscription to topics: ${this.topics.join(',')}. Transaction: ${ + l.transactionHash + }` + ) return [] } - const transaction: TransactionResponse = await this.l1Provider.getTransaction(l.transactionHash) + const transaction: TransactionResponse = await this.l1Provider.getTransaction( + l.transactionHash + ) const parsedTransactions: L1ToL2Transaction[] = [] for (const topic of matchedTopics) { const context = this.topicMap.get(topic) - if (!addressesAreEqual(l.address,context.contractAddress)) { + if (!addressesAreEqual(l.address, context.contractAddress)) { continue } const transactions = await context.parseL2Transactions(transaction) diff --git a/packages/rollup-core/src/app/util/environment.ts b/packages/rollup-core/src/app/util/environment.ts index 7399360bf69a..b937e5b28419 100644 --- a/packages/rollup-core/src/app/util/environment.ts +++ b/packages/rollup-core/src/app/util/environment.ts @@ -198,5 +198,4 @@ export class Environment { public static transactionBatchSubmissionMethodId(defaultValue?: string) { return process.env.TRANSACTION_BATCH_SUBMISSION_METHOD_ID || defaultValue } - } diff --git a/packages/rollup-core/src/app/utils.ts b/packages/rollup-core/src/app/utils.ts index 22fa99bffcfd..caf971e8941d 100644 --- a/packages/rollup-core/src/app/utils.ts +++ b/packages/rollup-core/src/app/utils.ts @@ -1,10 +1,10 @@ /* External Imports */ -import {bufToHexString, remove0x} from '@eth-optimism/core-utils' +import { bufToHexString, remove0x } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Wallet } from 'ethers' /* Internal Imports */ -import {Address, EVMBytecode, EVMOpcodeAndBytes, Opcode} from '../types' +import { Address, EVMBytecode, EVMOpcodeAndBytes, Opcode } from '../types' /** * Creates an unsigned transaction and returns its calldata. diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index dd779781e721..5b1b0d118b93 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -1,7 +1,7 @@ /* External Imports */ import { BigNumber } from '@eth-optimism/core-utils' -import {TransactionResponse} from 'ethers/providers/abstract-provider' +import { TransactionResponse } from 'ethers/providers/abstract-provider' // TODO: Probably not necessary? // Maybe just a map from token -> contract slot index (e.g. {ETH: 1, BAT: 2, REP: 3})? @@ -32,7 +32,9 @@ export interface TimestampedL1ToL2Transactions { transactions: L1ToL2Transaction[] } -export type L1ToL2TransactionParser = (transaction: TransactionResponse) => Promise +export type L1ToL2TransactionParser = ( + transaction: TransactionResponse +) => Promise export interface L1ToL2TransactionLogParserContext { topic: string contractAddress: Address From f99f4d513c96123b028fddcfe7deb8eb80a910dc Mon Sep 17 00:00:00 2001 From: Will Meister Date: Tue, 16 Jun 2020 09:56:37 -0500 Subject: [PATCH 5/9] Adding executable script for State Synchronizer --- .../l1-to-l2-state-synchronizer/exec/index.ts | 1 + .../exec/l1-to-l2-transaction-processor.ts | 20 ++- ...s => l1-to-l2-transaction-synchronizer.ts} | 114 ++++++++---------- packages/rollup-core/src/app/index.ts | 1 + .../app/l1-to-l2-transaction-synchronizer.ts | 2 + .../rollup-core/src/app/util/environment.ts | 26 ++-- 6 files changed, 74 insertions(+), 90 deletions(-) rename packages/l1-to-l2-state-synchronizer/exec/{l1-to-l2-transaction-batch-processor.ts => l1-to-l2-transaction-synchronizer.ts} (52%) diff --git a/packages/l1-to-l2-state-synchronizer/exec/index.ts b/packages/l1-to-l2-state-synchronizer/exec/index.ts index b869c8c66436..192e70aa785c 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/index.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/index.ts @@ -1 +1,2 @@ export * from './l1-to-l2-transaction-processor' +export * from './l1-to-l2-transaction-synchronizer' diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts index 8a877333db2a..f0bbc02af678 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts @@ -10,9 +10,7 @@ import { add0x, getLogger, logError } from '@eth-optimism/core-utils' import { Environment, initializeL1Node, - initializeL2Node, L1NodeContext, - L2NodeContext, L1ToL2TransactionProcessor, L1ToL2TransactionEventName, L1ToL2TransactionListener, @@ -20,7 +18,7 @@ import { CHAIN_ID, } from '@eth-optimism/rollup-core' -import { JsonRpcProvider, Provider, Web3Provider } from 'ethers/providers' +import { JsonRpcProvider, Provider } from 'ethers/providers' import * as fs from 'fs' import * as rimraf from 'rimraf' import { Wallet } from 'ethers' @@ -116,7 +114,7 @@ const getDB = (isTestMode: boolean = false): DB => { if (isTestMode) { return newInMemoryDB() } else { - if (!Environment.l1ToL2TxProcessorPersistentDbPath()) { + if (!Environment.stateSynchronizerPersistentDbPath()) { log.error( `No L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH environment variable present. Please set one!` ) @@ -124,7 +122,7 @@ const getDB = (isTestMode: boolean = false): DB => { } return new BaseDB( - getLevelInstance(Environment.l1ToL2TxProcessorPersistentDbPath()) + getLevelInstance(Environment.stateSynchronizerPersistentDbPath()) ) } } @@ -139,9 +137,9 @@ const getDB = (isTestMode: boolean = false): DB => { */ const getWallet = (provider: JsonRpcProvider): Wallet => { let wallet: Wallet - if (!!Environment.l1ToL2TxProcessorPrivateKey()) { + if (!!Environment.stateSynchronizerPrivateKey()) { wallet = new Wallet( - add0x(Environment.l1ToL2TxProcessorPrivateKey()), + add0x(Environment.stateSynchronizerPrivateKey()), provider ) log.info( @@ -178,9 +176,9 @@ const initializeDBPaths = (isTestMode: boolean) => { } else { if (Environment.clearDataKey() && !fs.existsSync(getClearDataFilePath())) { log.info(`Detected change in CLEAR_DATA_KEY. Purging data...`) - rimraf.sync(`${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}`) + rimraf.sync(`${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}`) log.info( - `L2 RPC Server data purged from '${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}'` + `L2 RPC Server data purged from '${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}'` ) makeDataDirectory() } @@ -191,7 +189,7 @@ const initializeDBPaths = (isTestMode: boolean) => { * Makes the data directory for this full node and adds a clear data key file if it is configured to use one. */ const makeDataDirectory = () => { - fs.mkdirSync(Environment.l1ToL2TxProcessorPersistentDbPath(), { + fs.mkdirSync(Environment.stateSynchronizerPersistentDbPath(), { recursive: true, }) if (Environment.clearDataKey()) { @@ -200,7 +198,7 @@ const makeDataDirectory = () => { } const getClearDataFilePath = () => { - return `${Environment.l1ToL2TxProcessorPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` + return `${Environment.stateSynchronizerPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` } if (typeof require !== 'undefined' && require.main === module) { diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts similarity index 52% rename from packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts rename to packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts index 3ef92b126313..c59c9121284b 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-batch-processor.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts @@ -2,7 +2,7 @@ import { BaseDB, DB, - EthereumEventProcessor, + EthereumBlockProcessor, getLevelInstance, newInMemoryDB, } from '@eth-optimism/core-db' @@ -10,19 +10,12 @@ import { add0x, getLogger, logError } from '@eth-optimism/core-utils' import { Environment, initializeL1Node, - initializeL2Node, L1NodeContext, - L2NodeContext, - L1ToL2TransactionBatchProcessor, - L1ToL2TransactionEventName, - L1ToL2TransactionListener, - L1ToL2TransactionBatchListenerSubmitter, CHAIN_ID, - L1ToL2TransactionBatchEventName, - L1ToL2TransactionBatchListener, + L1ToL2TransactionSynchronizer, } from '@eth-optimism/rollup-core' -import { JsonRpcProvider, Provider, Web3Provider } from 'ethers/providers' +import { JsonRpcProvider, Provider } from 'ethers/providers' import * as fs from 'fs' import * as rimraf from 'rimraf' import { Wallet } from 'ethers' @@ -30,19 +23,19 @@ import { getWallets } from 'ethereum-waffle' const log = getLogger('l1-to-l2-transaction-batch-processor') -export const runTest = async ( +export const runTestStateSynchronizer = async ( l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { - return run(true, l1Provider, l2Provider) +): Promise => { + return runStateSynchronizer(true, l1Provider, l2Provider) } -export const run = async ( - testFullNode: boolean = false, +export const runStateSynchronizer = async ( + testMode: boolean = false, l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { - initializeDBPaths(testFullNode) +): Promise => { + initializeDBPaths(testMode) let l1NodeContext: L1NodeContext log.info(`Attempting to connect to L1 Node.`) @@ -59,58 +52,45 @@ export const run = async ( provider = new JsonRpcProvider(Environment.l2NodeWeb3Url(), CHAIN_ID) } - const l2TransactionBatchListenerSubmitter = new L1ToL2TransactionBatchListenerSubmitter( - getWallet(provider), - provider, - Environment.transactionBatchSubmissionToAddress(), - Environment.transactionBatchSubmissionMethodId() - ) - - return getL1ToL2TransactionBatchProcessor( - testFullNode, - l1NodeContext, - l2TransactionBatchListenerSubmitter - ) + return getL1ToL2TransactionBatchProcessor(testMode, l1NodeContext, provider) } /** - * Gets an L1ToL2TransactionBatchProcessor based on configuration and the provided arguments. - * - * Notably this will return undefined if configuration says not to connect to the L1 node. + * Gets an L1ToL2TransactionSynchronizer based on configuration and the provided arguments. * - * @param testFullnode Whether or not this is a test full node. + * @param testMode Whether or not this is running as a test * @param l1NodeContext The L1 node context. - * @param listener The listener to listen to the processor. - * @returns The L1ToL2TransactionBatchProcessor or undefined. + * @param l2Provider The L2 JSON RPC Provider to use to communicate with the L2 node. + * @returns The L1ToL2TransactionSynchronizer. */ const getL1ToL2TransactionBatchProcessor = async ( - testFullnode: boolean, + testMode: boolean, l1NodeContext: L1NodeContext, - listener: L1ToL2TransactionBatchListener -): Promise => { - const db: DB = getDB(testFullnode) - const l1ToL2TransactionBatchProcessor: L1ToL2TransactionBatchProcessor = await L1ToL2TransactionBatchProcessor.create( + l2Provider: JsonRpcProvider +): Promise => { + const db: DB = getDB(testMode) + + const stateSynchronizer = await L1ToL2TransactionSynchronizer.create( db, - EthereumEventProcessor.getEventID( - // TODO: Figure out config / deployment of Transaction Batch publisher contract - // it will likely not be the l1ToL2TransactionPasser contract. - l1NodeContext.l1ToL2TransactionPasser.address, - L1ToL2TransactionBatchEventName - ), - [listener] + l1NodeContext.provider, + getL2Wallet(l2Provider), + [] // TODO: fill this in ) const earliestBlock = Environment.l1EarliestBlock() - const eventProcessor = new EthereumEventProcessor(db, earliestBlock) - await eventProcessor.subscribe( - // TODO: See above TODO - l1NodeContext.l1ToL2TransactionPasser, - L1ToL2TransactionBatchEventName, - l1ToL2TransactionBatchProcessor + const blockProcessor = new EthereumBlockProcessor( + db, + earliestBlock, + Environment.stateSynchronizerNumConfirmsRequired() + ) + await blockProcessor.subscribe( + l1NodeContext.provider, + stateSynchronizer, + true ) - return l1ToL2TransactionBatchProcessor + return stateSynchronizer } /** @@ -123,15 +103,15 @@ const getDB = (isTestMode: boolean = false): DB => { if (isTestMode) { return newInMemoryDB() } else { - if (!Environment.l1ToL2TxProcessorPersistentDbPath()) { + if (!Environment.stateSynchronizerPersistentDbPath()) { log.error( - `No L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH environment variable present. Please set one!` + `No L1_TO_L2_STATE_SYNCHRONIZER_PERSISTENT_DB_PATH environment variable present. Please set one!` ) process.exit(1) } return new BaseDB( - getLevelInstance(Environment.l1ToL2TxProcessorPersistentDbPath()) + getLevelInstance(Environment.stateSynchronizerPersistentDbPath()) ) } } @@ -144,15 +124,15 @@ const getDB = (isTestMode: boolean = false): DB => { * @param provider The provider with which the wallet will be associated. * @returns The wallet to use with the L2 node. */ -const getWallet = (provider: JsonRpcProvider): Wallet => { +const getL2Wallet = (provider: JsonRpcProvider): Wallet => { let wallet: Wallet - if (!!Environment.l1ToL2TxProcessorPrivateKey()) { + if (!!Environment.stateSynchronizerPrivateKey()) { wallet = new Wallet( - add0x(Environment.l1ToL2TxProcessorPrivateKey()), + add0x(Environment.stateSynchronizerPrivateKey()), provider ) log.info( - `Initialized L1-to-L2 Tx processor wallet from private key. Address: ${wallet.address}` + `Initialized State Synchronizer wallet from private key. Address: ${wallet.address}` ) } else { wallet = getWallets(provider)[0] @@ -162,11 +142,11 @@ const getWallet = (provider: JsonRpcProvider): Wallet => { } if (!wallet) { - const msg: string = `Wallet not created! Specify the L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY environment variable to set one!` + const msg: string = `Wallet not created! Specify the L1_TO_L2_STATE_SYNCHRONIZER_PRIVATE_KEY environment variable to set one!` log.error(msg) throw Error(msg) } else { - log.info(`L1-to-L2 Tx processor wallet created. Address: ${wallet.address}`) + log.info(`State Synchronizer wallet created. Address: ${wallet.address}`) } return wallet @@ -185,9 +165,9 @@ const initializeDBPaths = (isTestMode: boolean) => { } else { if (Environment.clearDataKey() && !fs.existsSync(getClearDataFilePath())) { log.info(`Detected change in CLEAR_DATA_KEY. Purging data...`) - rimraf.sync(`${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}`) + rimraf.sync(`${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}`) log.info( - `L2 RPC Server data purged from '${Environment.l1ToL2TxProcessorPersistentDbPath()}/{*,.*}'` + `L2 RPC Server data purged from '${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}'` ) makeDataDirectory() } @@ -198,7 +178,7 @@ const initializeDBPaths = (isTestMode: boolean) => { * Makes the data directory for this full node and adds a clear data key file if it is configured to use one. */ const makeDataDirectory = () => { - fs.mkdirSync(Environment.l1ToL2TxProcessorPersistentDbPath(), { + fs.mkdirSync(Environment.stateSynchronizerPersistentDbPath(), { recursive: true, }) if (Environment.clearDataKey()) { @@ -207,7 +187,7 @@ const makeDataDirectory = () => { } const getClearDataFilePath = () => { - return `${Environment.l1ToL2TxProcessorPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` + return `${Environment.stateSynchronizerPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` } if (typeof require !== 'undefined' && require.main === module) { diff --git a/packages/rollup-core/src/app/index.ts b/packages/rollup-core/src/app/index.ts index f12190fb4cba..68f121f86791 100644 --- a/packages/rollup-core/src/app/index.ts +++ b/packages/rollup-core/src/app/index.ts @@ -6,4 +6,5 @@ export * from './l1-to-l2-transaction-batch-listener-submitter' export * from './l1-to-l2-transaction-batch-processor' export * from './l1-to-l2-transaction-listener-submitter' export * from './l1-to-l2-transaction-processor' +export * from './l1-to-l2-transaction-synchronizer' export * from './utils' diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts index 383eef51bdc4..7d3e534a36b3 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts +++ b/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts @@ -98,6 +98,8 @@ export class L1ToL2TransactionSynchronizer topics: this.topics, }) + logs.sort((a, b) => a.logIndex - b.logIndex) + const l1ToL2TransactionArrays: L1ToL2Transaction[][] = await Promise.all( logs.map((l) => this.getTransactionsFromLog(l)) ) diff --git a/packages/rollup-core/src/app/util/environment.ts b/packages/rollup-core/src/app/util/environment.ts index b937e5b28419..fe54293830f3 100644 --- a/packages/rollup-core/src/app/util/environment.ts +++ b/packages/rollup-core/src/app/util/environment.ts @@ -183,19 +183,21 @@ export class Environment { : defaultValue } - // L1 to L2 Tx Processor Config - public static l1ToL2TxProcessorPersistentDbPath(defaultValue?: string) { - return process.env.L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH || defaultValue - } - public static l1ToL2TxProcessorPrivateKey(defaultValue?: string) { - return process.env.L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY || defaultValue + // State Synchronizer Config + public static stateSynchronizerPersistentDbPath( + defaultValue?: string + ): string { + return process.env.STATE_SYNCHRONIZER_PERSISTENT_DB_PATH || defaultValue } - - // L1 to L2 Tx Batch Processor Config - public static transactionBatchSubmissionToAddress(defaultValue?: string) { - return process.env.TRANSACTION_BATCH_SUBMISSION_TO_ADDRESS || defaultValue + public static stateSynchronizerPrivateKey(defaultValue?: string): string { + return process.env.STATE_SYNCHRONIZER_PRIVATE_KEY || defaultValue } - public static transactionBatchSubmissionMethodId(defaultValue?: string) { - return process.env.TRANSACTION_BATCH_SUBMISSION_METHOD_ID || defaultValue + public static stateSynchronizerNumConfirmsRequired( + defaultValue: string = '1' + ): number { + return parseInt( + process.env.STATE_SYNCHRONIZER_NUM_CONFIRMS_REQUIRED || defaultValue, + 10 + ) } } From e995036304a1293b38c91ccfd9a065a9d38681fd Mon Sep 17 00:00:00 2001 From: Will Meister Date: Tue, 16 Jun 2020 17:55:47 -0500 Subject: [PATCH 6/9] Cleaning up state synchronizer executable, separating tx batch processor vs listener, removing old event handling code that will not be used --- .../l1-to-l2-state-synchronizer/exec/index.ts | 1 - .../exec/l1-to-l2-transaction-processor.ts | 206 ---------- .../exec/l1-to-l2-transaction-synchronizer.ts | 20 +- .../l1-to-l2-transaction-processor.spec.ts | 72 ---- .../L1ToL2TransactionEvents.sol | 20 + packages/rollup-core/src/app/index.ts | 6 +- .../src/app/l1-to-l2-event-handling/index.ts | 2 + .../l1-transaction-batch-processor.ts} | 99 ++--- .../l2-transaction-batch-submitter.ts | 62 +++ ...l2-transaction-batch-listener-submitter.ts | 137 ------- .../l1-to-l2-transaction-batch-processor.ts | 160 -------- ...l1-to-l2-transaction-listener-submitter.ts | 81 ---- .../src/app/l1-to-l2-transaction-processor.ts | 114 ------ .../rollup-core/src/app/util/environment.ts | 3 - .../src/types/l1-to-l2-listener.ts | 20 +- packages/rollup-core/src/types/types.ts | 25 +- ...-to-l2-transaction-batch-processor.spec.ts | 372 ++++++++++++++++++ .../l1-to-l2-transaction-processor.spec.ts | 233 ----------- .../src/app/web3-rpc-handler.ts | 4 +- .../rollup-full-node/src/exec/fullnode.ts | 60 --- .../test/l1-to-l2-transactions.spec.ts | 127 ------ 21 files changed, 512 insertions(+), 1312 deletions(-) delete mode 100644 packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts delete mode 100644 packages/l1-to-l2-state-synchronizer/test/l1-to-l2-transaction-processor.spec.ts create mode 100644 packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol create mode 100644 packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts rename packages/rollup-core/src/app/{l1-to-l2-transaction-synchronizer.ts => l1-to-l2-event-handling/l1-transaction-batch-processor.ts} (66%) create mode 100644 packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts delete mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts delete mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts delete mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts delete mode 100644 packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts create mode 100644 packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts delete mode 100644 packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts delete mode 100644 packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts diff --git a/packages/l1-to-l2-state-synchronizer/exec/index.ts b/packages/l1-to-l2-state-synchronizer/exec/index.ts index 192e70aa785c..00cff5cbc9eb 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/index.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/index.ts @@ -1,2 +1 @@ -export * from './l1-to-l2-transaction-processor' export * from './l1-to-l2-transaction-synchronizer' diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts deleted file mode 100644 index f0bbc02af678..000000000000 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-processor.ts +++ /dev/null @@ -1,206 +0,0 @@ -/* External Imports */ -import { - BaseDB, - DB, - EthereumEventProcessor, - getLevelInstance, - newInMemoryDB, -} from '@eth-optimism/core-db' -import { add0x, getLogger, logError } from '@eth-optimism/core-utils' -import { - Environment, - initializeL1Node, - L1NodeContext, - L1ToL2TransactionProcessor, - L1ToL2TransactionEventName, - L1ToL2TransactionListener, - L1ToL2TransactionListenerSubmitter, - CHAIN_ID, -} from '@eth-optimism/rollup-core' - -import { JsonRpcProvider, Provider } from 'ethers/providers' -import * as fs from 'fs' -import * as rimraf from 'rimraf' -import { Wallet } from 'ethers' -import { getWallets } from 'ethereum-waffle' - -const log = getLogger('l1-to-l2-transaction-processor') - -export const runTest = async ( - l1Provider?: Provider, - l2Provider?: JsonRpcProvider -): Promise => { - return run(true, l1Provider, l2Provider) -} - -export const run = async ( - testFullNode: boolean = false, - l1Provider?: Provider, - l2Provider?: JsonRpcProvider -): Promise => { - initializeDBPaths(testFullNode) - - let l1NodeContext: L1NodeContext - log.info(`Attempting to connect to L1 Node.`) - try { - l1NodeContext = await initializeL1Node(true, l1Provider) - } catch (e) { - logError(log, 'Error connecting to L1 Node', e) - throw e - } - - let provider: JsonRpcProvider = l2Provider - if (!provider && !!Environment.l2NodeWeb3Url()) { - log.info(`Connecting to L2 web3 URL: ${Environment.l2NodeWeb3Url()}`) - provider = new JsonRpcProvider(Environment.l2NodeWeb3Url(), CHAIN_ID) - } - - const l2TransactionListenerSubmitter = new L1ToL2TransactionListenerSubmitter( - getWallet(provider), - provider - ) - - return getL1ToL2TransactionProcessor( - testFullNode, - l1NodeContext, - l2TransactionListenerSubmitter - ) -} - -/** - * Gets an L1ToL2TransactionProcessor based on configuration and the provided arguments. - * - * Notably this will return undefined if configuration says not to connect to the L1 node. - * - * @param testFullnode Whether or not this is a test full node. - * @param l1NodeContext The L1 node context. - * @param listener The listener to listen to the processor. - * @returns The L1ToL2TransactionProcessor or undefined. - */ -const getL1ToL2TransactionProcessor = async ( - testFullnode: boolean, - l1NodeContext: L1NodeContext, - listener: L1ToL2TransactionListener -): Promise => { - const db: DB = getDB(testFullnode) - const l1ToL2TransactionProcessor: L1ToL2TransactionProcessor = await L1ToL2TransactionProcessor.create( - db, - EthereumEventProcessor.getEventID( - l1NodeContext.l1ToL2TransactionPasser.address, - L1ToL2TransactionEventName - ), - [listener] - ) - - const earliestBlock = Environment.l1EarliestBlock() - - const eventProcessor = new EthereumEventProcessor(db, earliestBlock) - await eventProcessor.subscribe( - l1NodeContext.l1ToL2TransactionPasser, - L1ToL2TransactionEventName, - l1ToL2TransactionProcessor - ) - - return l1ToL2TransactionProcessor -} - -/** - * Gets the appropriate db for this node to use based on whether or not this is run in test mode. - * - * @param isTestMode Whether or not it is test mode. - * @returns The constructed DB instance. - */ -const getDB = (isTestMode: boolean = false): DB => { - if (isTestMode) { - return newInMemoryDB() - } else { - if (!Environment.stateSynchronizerPersistentDbPath()) { - log.error( - `No L1_TO_L2_TX_PROCESSOR_PERSISTENT_DB_PATH environment variable present. Please set one!` - ) - process.exit(1) - } - - return new BaseDB( - getLevelInstance(Environment.stateSynchronizerPersistentDbPath()) - ) - } -} - -/** - * Gets the wallet to use to interact with the L2 node. This may be configured via - * private key file specified through environment variables. If not it is assumed - * that a local test provider is being used, from which the wallet may be fetched. - * - * @param provider The provider with which the wallet will be associated. - * @returns The wallet to use with the L2 node. - */ -const getWallet = (provider: JsonRpcProvider): Wallet => { - let wallet: Wallet - if (!!Environment.stateSynchronizerPrivateKey()) { - wallet = new Wallet( - add0x(Environment.stateSynchronizerPrivateKey()), - provider - ) - log.info( - `Initialized L1-to-L2 Tx processor wallet from private key. Address: ${wallet.address}` - ) - } else { - wallet = getWallets(provider)[0] - log.info( - `Getting wallet from provider. First wallet private key: [${wallet.privateKey}` - ) - } - - if (!wallet) { - const msg: string = `Wallet not created! Specify the L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY environment variable to set one!` - log.error(msg) - throw Error(msg) - } else { - log.info(`L1-to-L2 Tx processor wallet created. Address: ${wallet.address}`) - } - - return wallet -} - -/** - * Initializes filesystem DB paths. This will also purge all data if the `CLEAR_DATA_KEY` has changed. - */ -const initializeDBPaths = (isTestMode: boolean) => { - if (isTestMode) { - return - } - - if (!fs.existsSync(Environment.l2RpcServerPersistentDbPath())) { - makeDataDirectory() - } else { - if (Environment.clearDataKey() && !fs.existsSync(getClearDataFilePath())) { - log.info(`Detected change in CLEAR_DATA_KEY. Purging data...`) - rimraf.sync(`${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}`) - log.info( - `L2 RPC Server data purged from '${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}'` - ) - makeDataDirectory() - } - } -} - -/** - * Makes the data directory for this full node and adds a clear data key file if it is configured to use one. - */ -const makeDataDirectory = () => { - fs.mkdirSync(Environment.stateSynchronizerPersistentDbPath(), { - recursive: true, - }) - if (Environment.clearDataKey()) { - fs.writeFileSync(getClearDataFilePath(), '') - } -} - -const getClearDataFilePath = () => { - return `${Environment.stateSynchronizerPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` -} - -if (typeof require !== 'undefined' && require.main === module) { - run() -} diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts index c59c9121284b..7b7408b78e38 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts @@ -12,7 +12,7 @@ import { initializeL1Node, L1NodeContext, CHAIN_ID, - L1ToL2TransactionSynchronizer, + L1TransactionBatchProcessor, } from '@eth-optimism/rollup-core' import { JsonRpcProvider, Provider } from 'ethers/providers' @@ -23,18 +23,18 @@ import { getWallets } from 'ethereum-waffle' const log = getLogger('l1-to-l2-transaction-batch-processor') -export const runTestStateSynchronizer = async ( +export const runTest = async ( l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { - return runStateSynchronizer(true, l1Provider, l2Provider) +): Promise => { + return run(true, l1Provider, l2Provider) } -export const runStateSynchronizer = async ( +export const run = async ( testMode: boolean = false, l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { +): Promise => { initializeDBPaths(testMode) let l1NodeContext: L1NodeContext @@ -67,14 +67,14 @@ const getL1ToL2TransactionBatchProcessor = async ( testMode: boolean, l1NodeContext: L1NodeContext, l2Provider: JsonRpcProvider -): Promise => { +): Promise => { const db: DB = getDB(testMode) - const stateSynchronizer = await L1ToL2TransactionSynchronizer.create( + const stateSynchronizer = await L1TransactionBatchProcessor.create( db, l1NodeContext.provider, - getL2Wallet(l2Provider), - [] // TODO: fill this in + [], // TODO: fill this in + [] // TODO: Fill this in ) const earliestBlock = Environment.l1EarliestBlock() diff --git a/packages/l1-to-l2-state-synchronizer/test/l1-to-l2-transaction-processor.spec.ts b/packages/l1-to-l2-state-synchronizer/test/l1-to-l2-transaction-processor.spec.ts deleted file mode 100644 index 533886e2eb1b..000000000000 --- a/packages/l1-to-l2-state-synchronizer/test/l1-to-l2-transaction-processor.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import './setup' - -/* External Imports */ -import { - deployContract, - L1ToL2TransactionProcessor, - getUnsignedTransactionCalldata, - Environment, -} from '@eth-optimism/rollup-core' -import { TestSimpleStorageContractDefinition } from '@eth-optimism/rollup-contracts' -import { add0x, sleep } from '@eth-optimism/core-utils' -import { FullnodeContext, runFullnode } from '@eth-optimism/rollup-full-node' - -import { Contract, Wallet } from 'ethers' -import { JsonRpcProvider } from 'ethers/providers' - -/* Internal Imports */ -import { runTest } from '../exec' - -const storageKey: string = '0x' + '01'.repeat(32) -const storageValue: string = '0x' + '22'.repeat(32) - -describe('L1 To L2 Tx Processor Integration Tests', () => { - let fullnodeContext: FullnodeContext - let l2SimpleStorage: Contract - let txProcessor: L1ToL2TransactionProcessor - - beforeEach(async () => { - process.env.NO_L1_TO_L2_TX_PROCESSOR = '1' - fullnodeContext = await runFullnode(true) - const fullNodeProvider = new JsonRpcProvider( - `http://${Environment.l2RpcServerHost()}:${Environment.l2RpcServerPort()}` - ) - - const wallet = Wallet.createRandom().connect(fullNodeProvider) - l2SimpleStorage = await deployContract( - wallet, - TestSimpleStorageContractDefinition, - [] - ) - - process.env.L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY = Wallet.createRandom().privateKey - txProcessor = await runTest( - fullnodeContext.l1NodeContext.provider, - fullNodeProvider - ) - }) - - afterEach(async () => { - delete process.env.NO_L1_TO_L2_TX_PROCESSOR - delete process.env.L1_TO_L2_TX_PROCESSOR_PRIVATE_KEY - }) - - it('Receives and executes L1 to L2 Tx', async () => { - const ovmEntrypoint = l2SimpleStorage.address - const ovmCalldata: string = getUnsignedTransactionCalldata( - l2SimpleStorage, - 'setStorage', - [storageKey, storageValue] - ) - - await fullnodeContext.l1NodeContext.l1ToL2TransactionPasser.passTransactionToL2( - add0x(ovmEntrypoint), - ovmCalldata - ) - - await sleep(8_000) - - const res = await l2SimpleStorage.getStorage(storageKey) - res.should.equal(storageValue, `L1 Transaction did not flow through!`) - }) -}) diff --git a/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol b/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol new file mode 100644 index 000000000000..e76d18b753bb --- /dev/null +++ b/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +contract L1ToL2TransactionEvents { + event L1ToL2Transaction(); + event SlowQueueTransaction(); + event CanonicalTransactionChainBatch(); + + function sendL1ToL2Transaction(bytes memory _calldata) public { + emit L1ToL2Transaction(); + } + + function sendSlowQueueTransaction(bytes memory _calldata) public { + emit SlowQueueTransaction(); + } + + function sendCanonicalTransactionChainBatch(bytes memory _calldata) public { + emit CanonicalTransactionChainBatch(); + } +} diff --git a/packages/rollup-core/src/app/index.ts b/packages/rollup-core/src/app/index.ts index 68f121f86791..3df401d097ab 100644 --- a/packages/rollup-core/src/app/index.ts +++ b/packages/rollup-core/src/app/index.ts @@ -1,10 +1,6 @@ +export * from './l1-to-l2-event-handling' export * from './serialization' export * from './util' export * from './constants' -export * from './l1-to-l2-transaction-batch-listener-submitter' -export * from './l1-to-l2-transaction-batch-processor' -export * from './l1-to-l2-transaction-listener-submitter' -export * from './l1-to-l2-transaction-processor' -export * from './l1-to-l2-transaction-synchronizer' export * from './utils' diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts new file mode 100644 index 000000000000..ea8fb711d3fc --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts @@ -0,0 +1,2 @@ +export * from './l2-transaction-batch-submitter' +export * from './l1-transaction-batch-processor' diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts similarity index 66% rename from packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts rename to packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts index 7d3e534a36b3..71cc77bd6021 100644 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-synchronizer.ts +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts @@ -4,42 +4,28 @@ import { DB, EthereumListener, } from '@eth-optimism/core-db' -import { - getLogger, - Logger, - numberToHexString, - remove0x, -} from '@eth-optimism/core-utils' +import { getLogger, Logger } from '@eth-optimism/core-utils' /* Internal Imports */ import { L1ToL2Transaction, - TimestampedL1ToL2Transactions, + L1ToL2TransactionBatch, + L1ToL2TransactionBatchListener, L1ToL2TransactionLogParserContext, -} from '../types' -import { - Block, - JsonRpcProvider, - Provider, - TransactionResponse, -} from 'ethers/providers' +} from '../../types' +import { Block, Provider, TransactionResponse } from 'ethers/providers' import { Log } from 'ethers/providers/abstract-provider' -import { Wallet } from 'ethers' -import { addressesAreEqual } from './utils' +import { addressesAreEqual } from '../utils' const log: Logger = getLogger('l1-to-l2-transition-synchronizer') -// params: [timestampHex, transactionsArrayJSON, signedTransactionsArrayJSON] -const sendL1ToL2TransactionsMethod: string = 'optimism_sendL1ToL2Transactions' - -export class L1ToL2TransactionSynchronizer - extends BaseQueuedPersistedProcessor +export class L1TransactionBatchProcessor + extends BaseQueuedPersistedProcessor implements EthereumListener { public static readonly persistenceKey = 'L1ToL2TransactionSynchronizer' private readonly topics: string[] private readonly topicMap: Map - private readonly l2Provider: JsonRpcProvider /** * Creates a L1ToL2TransactionSynchronizer that subscribes to blocks, processes all @@ -47,23 +33,23 @@ export class L1ToL2TransactionSynchronizer * * @param db The DB to use to persist the queue of L1ToL2Transaction[] objects. * @param l1Provider The provider to use to connect to L1 to subscribe & fetch block / tx / log data. - * @param l2Wallet The L2 wallet to use to submit transactions ** ASSUMED TO BE CONNECTED TO THE L2 JSON RPC PROVIDER ** * @param logContexts The collection of L1ToL2TransactionLogParserContext that uniquely identify the log event and * provide the ability to create L2 transactions from the L1 transaction that emitted it. + * @param listeners The downstream subscribers to the L1ToL2TransactionBatch objects this processor creates. * @param persistenceKey The persistence key to use for this instance within the provided DB. */ public static async create( db: DB, l1Provider: Provider, - l2Wallet: Wallet, logContexts: L1ToL2TransactionLogParserContext[], - persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey - ): Promise { - const processor = new L1ToL2TransactionSynchronizer( + listeners: L1ToL2TransactionBatchListener[], + persistenceKey: string = L1TransactionBatchProcessor.persistenceKey + ): Promise { + const processor = new L1TransactionBatchProcessor( db, l1Provider, - l2Wallet, logContexts, + listeners, persistenceKey ) await processor.init() @@ -73,16 +59,15 @@ export class L1ToL2TransactionSynchronizer private constructor( db: DB, private readonly l1Provider: Provider, - private readonly l2Wallet: Wallet, logContexts: L1ToL2TransactionLogParserContext[], - persistenceKey: string = L1ToL2TransactionSynchronizer.persistenceKey + private readonly listeners: L1ToL2TransactionBatchListener[], + persistenceKey: string = L1TransactionBatchProcessor.persistenceKey ) { super(db, persistenceKey) this.topicMap = new Map( logContexts.map((x) => [x.topic, x]) ) this.topics = Array.from(this.topicMap.keys()) - this.l2Provider = l2Wallet.provider as JsonRpcProvider } /** @@ -97,6 +82,11 @@ export class L1ToL2TransactionSynchronizer blockHash: block.hash, topics: this.topics, }) + log.debug( + `Got ${logs.length} logs from block ${block.number}: ${JSON.stringify( + logs + )}` + ) logs.sort((a, b) => a.logIndex - b.logIndex) @@ -119,6 +109,7 @@ export class L1ToL2TransactionSynchronizer } this.add(block.number, { + blockNumber: block.number, timestamp: block.timestamp, transactions, }) @@ -136,42 +127,19 @@ export class L1ToL2TransactionSynchronizer */ protected async handleNextItem( blockNumber: number, - timestampedTransactions: TimestampedL1ToL2Transactions + transactionBatch: L1ToL2TransactionBatch ): Promise { try { - if ( - !timestampedTransactions.transactions || - !timestampedTransactions.transactions.length - ) { - log.debug(`Moving past empty block ${blockNumber}.`) - await this.markProcessed(blockNumber) - return + if (!!transactionBatch.transactions.length) { + this.listeners.map((x) => + x.handleL1ToL2TransactionBatch(transactionBatch) + ) } - - const timestamp: string = numberToHexString( - timestampedTransactions.timestamp - ) - const txs = JSON.stringify( - timestampedTransactions.transactions.map((x) => { - return { - nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', - sender: x.sender, - calldata: x.calldata, - } - }) - ) - const signedTxsArray: string = await this.l2Wallet.signMessage(txs) - await this.l2Provider.send(sendL1ToL2TransactionsMethod, [ - timestamp, - txs, - signedTxsArray, - ]) - await this.markProcessed(blockNumber) } catch (e) { this.logError( `Error processing L1ToL2Transactions. Txs: ${JSON.stringify( - timestampedTransactions + transactionBatch )}`, e ) @@ -183,9 +151,7 @@ export class L1ToL2TransactionSynchronizer /** * @inheritDoc */ - protected async serializeItem( - item: TimestampedL1ToL2Transactions - ): Promise { + protected async serializeItem(item: L1ToL2TransactionBatch): Promise { return Buffer.from(JSON.stringify(item), 'utf-8') } @@ -194,7 +160,7 @@ export class L1ToL2TransactionSynchronizer */ protected async deserializeItem( itemBuffer: Buffer - ): Promise { + ): Promise { return JSON.parse(itemBuffer.toString('utf-8')) } @@ -216,6 +182,9 @@ export class L1ToL2TransactionSynchronizer const transaction: TransactionResponse = await this.l1Provider.getTransaction( l.transactionHash ) + log.debug( + `Fetched tx by hash ${l.transactionHash}: ${JSON.stringify(transaction)}` + ) const parsedTransactions: L1ToL2Transaction[] = [] for (const topic of matchedTopics) { @@ -223,7 +192,7 @@ export class L1ToL2TransactionSynchronizer if (!addressesAreEqual(l.address, context.contractAddress)) { continue } - const transactions = await context.parseL2Transactions(transaction) + const transactions = await context.parseL2Transactions(l, transaction) parsedTransactions.push(...transactions) } diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts new file mode 100644 index 000000000000..41cb9cbc9376 --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts @@ -0,0 +1,62 @@ +/* External Imports */ +import { getLogger, Logger, numberToHexString } from '@eth-optimism/core-utils' + +/* Internal Imports */ +import { + L1ToL2TransactionBatch, + L1ToL2TransactionBatchListener, +} from '../../types' +import { JsonRpcProvider } from 'ethers/providers' +import { Wallet } from 'ethers' + +const log: Logger = getLogger('l1-to-l2-transition-synchronizer') + +// params: [timestampHex, transactionsArrayJSON, signedTransactionsArrayJSON] +const sendL1ToL2TransactionsMethod: string = 'optimism_sendL1ToL2Transactions' + +export class L2TransactionBatchSubmitter + implements L1ToL2TransactionBatchListener { + private readonly l2Provider: JsonRpcProvider + + constructor(private readonly l2Wallet: Wallet) { + this.l2Provider = l2Wallet.provider as JsonRpcProvider + } + + /** + * @inheritDoc + */ + public async handleL1ToL2TransactionBatch( + transactionBatch: L1ToL2TransactionBatch + ): Promise { + if (!transactionBatch) { + const msg = `Received undefined Transaction Batch!.` + log.error(msg) + throw msg + } + + if ( + !transactionBatch.transactions || + !transactionBatch.transactions.length + ) { + log.debug(`Moving past empty block ${transactionBatch.blockNumber}.`) + return + } + + const timestamp: string = numberToHexString(transactionBatch.timestamp) + const txs = JSON.stringify( + transactionBatch.transactions.map((x) => { + return { + nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', + sender: x.sender, + calldata: x.calldata, + } + }) + ) + const signedTxsArray: string = await this.l2Wallet.signMessage(txs) + await this.l2Provider.send(sendL1ToL2TransactionsMethod, [ + timestamp, + txs, + signedTxsArray, + ]) + } +} diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts deleted file mode 100644 index 83395c231325..000000000000 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-listener-submitter.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* External Imports */ -import { - add0x, - getLogger, - isValidHexAddress, - logError, - remove0x, -} from '@eth-optimism/core-utils' - -import { JsonRpcProvider } from 'ethers/providers' -import { Wallet } from 'ethers' - -/* Internal Imports */ -import { - Address, - L1ToL2TransactionBatch, - L1ToL2TransactionBatchListener, -} from '../types' -import { CHAIN_ID, GAS_LIMIT } from './constants' - -const log = getLogger('l1-to-l2-tx-batch-listener-submitter') - -/** - * Handles L1 To L2 TransactionBatches, submitting them to the configured L2 node. - */ -export class L1ToL2TransactionBatchListenerSubmitter - implements L1ToL2TransactionBatchListener { - private readonly submissionToAddress: Address - private readonly submissionMethodId: string - - constructor( - private readonly wallet: Wallet, - private readonly provider: JsonRpcProvider, - submissionToAddress: Address, - submissionMethodId: string - ) { - if (!submissionMethodId || remove0x(submissionMethodId).length !== 8) { - throw Error( - `Invalid Transaction Batch submission method ID: ${remove0x( - submissionMethodId - )}. Expected 4 bytes (8 hex chars).` - ) - } - if (!isValidHexAddress(submissionToAddress)) { - throw Error( - `Invalid Transaction Batch submission to address: ${remove0x( - submissionToAddress - )}. Expected 20 bytes (40 hex chars).` - ) - } - this.submissionToAddress = add0x(submissionToAddress) - this.submissionMethodId = add0x(submissionMethodId) - } - - public async handleTransactionBatch( - transactionBatch: L1ToL2TransactionBatch - ): Promise { - log.debug( - `Received L1 to L2 Transaction Batch ${JSON.stringify(transactionBatch)}` - ) - - const signedTx = await this.getSignedBatchTransaction(transactionBatch) - - log.debug( - `sending signed tx for batch. Tx: ${JSON.stringify( - transactionBatch - )}. Signed: ${add0x(signedTx)}` - ) - const receipt = await this.provider.sendTransaction(signedTx) - - log.debug( - `L1 to L2 Transaction Batch Tx submitted. Tx hash: ${ - receipt.hash - }. Tx batch: ${JSON.stringify(transactionBatch)}` - ) - try { - const txReceipt = await this.provider.waitForTransaction(receipt.hash) - if (!txReceipt || !txReceipt.status) { - const msg = `Error processing L1 to L2 Transaction Batch. Tx batch: ${JSON.stringify( - transactionBatch - )}, Receipt: ${JSON.stringify(receipt)}` - log.error(msg) - throw new Error(msg) - } - } catch (e) { - logError( - log, - `Error submitting L1 to L2 Transaction Batch to L2 node. Tx Hash: ${ - receipt.hash - }, Tx: ${JSON.stringify(transactionBatch)}`, - e - ) - throw e - } - log.debug(`L1 to L2 Transaction applied to L2. Tx hash: ${receipt.hash}`) - } - - /** - * Builds the necessary Transaction to send the provided L1ToL2TransactionBatch - * to the L2 node. - * - * @param transactionBatch The transaction batch to wrap in a signed Transaction - * @return The signed Transaction. - */ - private async getSignedBatchTransaction( - transactionBatch: L1ToL2TransactionBatch - ): Promise { - const tx = { - nonce: transactionBatch.nonce, - gasPrice: 0, - gasLimit: GAS_LIMIT, - to: this.submissionToAddress, - value: 0, - data: await this.getL2TransactionBatchCalldata(transactionBatch.calldata), - chainId: CHAIN_ID, - } - - return this.wallet.sign(tx) - } - - /** - * Gets the calldata for the Transaction to send to the L2 node. Specifically, this - * swaps out the first 4 bytes (Method ID) of the calldata in favor of the Method ID - * of the L2 node method to call to submit the batch. - * - * @param l1Calldata The L1 Calldata for the Transaction Batch. - */ - private async getL2TransactionBatchCalldata( - l1Calldata: string - ): Promise { - const l1CalldataParams = - !l1Calldata || remove0x(l1Calldata).length < 8 - ? 0 - : remove0x(l1Calldata).substr(8) - return `${this.submissionMethodId}${l1CalldataParams}` - } -} diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts deleted file mode 100644 index c5227b833000..000000000000 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-batch-processor.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* External Imports */ -import { - BaseQueuedPersistedProcessor, - DB, - EthereumEvent, - EthereumListener, -} from '@eth-optimism/core-db' -import { getLogger, logError, Logger } from '@eth-optimism/core-utils' - -/* Internal Imports */ -import { - L1ToL2Transaction, - L1ToL2TransactionBatch, - L1ToL2TransactionBatchListener, -} from '../types' -import { Provider, TransactionResponse } from 'ethers/providers' - -const log: Logger = getLogger('l1-to-l2-transition-batch-processor') - -export class L1ToL2TransactionBatchProcessor - extends BaseQueuedPersistedProcessor - implements EthereumListener { - public static readonly persistenceKey = 'L1ToL2TransitionBatchProcessor' - - private readonly provider: Provider - - public static async create( - db: DB, - l1ToL2EventId: string, - listeners: L1ToL2TransactionBatchListener[], - persistenceKey: string = L1ToL2TransactionBatchProcessor.persistenceKey - ): Promise { - const processor = new L1ToL2TransactionBatchProcessor( - db, - l1ToL2EventId, - listeners, - persistenceKey - ) - await processor.init() - return processor - } - - private constructor( - db: DB, - private readonly l1ToL2EventId: string, - private readonly listeners: L1ToL2TransactionBatchListener[], - persistenceKey: string = L1ToL2TransactionBatchProcessor.persistenceKey - ) { - super(db, persistenceKey) - } - - /** - * @inheritDoc - */ - public async handle(event: EthereumEvent): Promise { - if (event.eventID !== this.l1ToL2EventId || !event.values) { - log.debug( - `Received event of wrong ID or with incorrect values. Ignoring event: [${JSON.stringify( - event - )}]` - ) - return - } - - const calldata = await this.fetchCalldata(event.transactionHash) - - let transactions: L1ToL2Transaction[] = [] - try { - transactions = await this.parseTransactions(calldata) - } catch (e) { - // TODO: What do we do here? - logError( - log, - `Error parsing calldata for event ${JSON.stringify( - event - )}. Assuming this tx batch was malicious / invalid. Moving on.`, - e - ) - } - - const transactionBatch: L1ToL2TransactionBatch = { - nonce: event.values['_nonce'].toNumber(), - timestamp: event.values['_timestamp'].toNumber(), - transactions, - calldata, - } - - this.add(transactionBatch.nonce, transactionBatch) - } - - /** - * @inheritDoc - */ - public async onSyncCompleted(syncIdentifier?: string): Promise { - // no-op - } - - /** - * @inheritDoc - */ - protected async handleNextItem( - index: number, - item: L1ToL2TransactionBatch - ): Promise { - try { - await Promise.all( - this.listeners.map((x) => x.handleTransactionBatch(item)) - ) - await this.markProcessed(index) - } catch (e) { - this.logError( - `Error processing L1ToL2Transaction in at least one handler. Tx: ${JSON.stringify( - item - )}`, - e - ) - // All errors should be caught in the listeners, so this is fatal. - process.exit(1) - } - } - - /** - * @inheritDoc - */ - protected async serializeItem(item: L1ToL2TransactionBatch): Promise { - return Buffer.from(JSON.stringify(item), 'utf-8') - } - - /** - * @inheritDoc - */ - protected async deserializeItem( - itemBuffer: Buffer - ): Promise { - return JSON.parse(itemBuffer.toString('utf-8')) - } - - private async fetchCalldata(txHash: string): Promise { - let tx: TransactionResponse - try { - tx = await this.provider.getTransaction(txHash) - } catch (e) { - logError( - log, - `Error fetching tx hash ${txHash}. This should not ever happen.`, - e - ) - process.exit(1) - } - - return tx.data - } - - // TODO: This when a format is solidified - private async parseTransactions( - calldata: string - ): Promise { - return [] - } -} diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts deleted file mode 100644 index 8206e5d381a0..000000000000 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-listener-submitter.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* External Imports */ -import { add0x, getLogger, logError } from '@eth-optimism/core-utils' - -import { JsonRpcProvider } from 'ethers/providers' -import { Wallet } from 'ethers' - -/* Internal Imports */ -import { L1ToL2Transaction, L1ToL2TransactionListener } from '../types' -import { CHAIN_ID, GAS_LIMIT } from './constants' - -const log = getLogger('l1-to-l2-tx-listener') - -/** - * Handles L1 To L2 Transactions, submitting them to the configured L2 node. - */ -export class L1ToL2TransactionListenerSubmitter - implements L1ToL2TransactionListener { - constructor( - private readonly wallet: Wallet, - private readonly provider: JsonRpcProvider - ) {} - - public async handleL1ToL2Transaction( - transaction: L1ToL2Transaction - ): Promise { - log.debug(`Received L1 to L2 Transaction ${JSON.stringify(transaction)}`) - - const signedTx = await this.getSignedTransaction(transaction) - - log.debug( - `sending signed tx. Tx: ${JSON.stringify(transaction)}. Signed: ${add0x( - signedTx - )}` - ) - const receipt = await this.provider.sendTransaction(signedTx) - - log.debug( - `L1 to L2 Transaction submitted. Tx hash: ${ - receipt.hash - }. Tx: ${JSON.stringify(transaction)}` - ) - try { - const txReceipt = await this.provider.waitForTransaction(receipt.hash) - if (!txReceipt || !txReceipt.status) { - const msg = `Error processing L1 to L2 Transaction. Tx: ${JSON.stringify( - transaction - )}, Receipt: ${JSON.stringify(receipt)}` - log.error(msg) - throw new Error(msg) - } - } catch (e) { - logError( - log, - `Error submitting L1 to L2 transaction to L2 node. Tx Hash: ${ - receipt.hash - }, Tx: ${JSON.stringify(transaction)}`, - e - ) - throw e - } - log.debug(`L1 to L2 Transaction applied to L2. Tx hash: ${receipt.hash}`) - } - - private async getSignedTransaction( - transaction: L1ToL2Transaction - ): Promise { - // TODO: change / append to calldata to add sender? - - const tx = { - nonce: transaction.nonce, - gasPrice: 0, - gasLimit: GAS_LIMIT, - to: transaction.target, - value: 0, - data: add0x(transaction.calldata), - chainId: CHAIN_ID, - } - - return this.wallet.sign(tx) - } -} diff --git a/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts b/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts deleted file mode 100644 index 56e36d56f074..000000000000 --- a/packages/rollup-core/src/app/l1-to-l2-transaction-processor.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* External Imports */ -import { - BaseQueuedPersistedProcessor, - DB, - EthereumEvent, - EthereumListener, -} from '@eth-optimism/core-db' -import { BigNumber, getLogger, Logger } from '@eth-optimism/core-utils' - -/* Internal Imports */ -import { L1ToL2Transaction, L1ToL2TransactionListener } from '../types' - -const log: Logger = getLogger('l1-to-l2-transaction-processor') - -export class L1ToL2TransactionProcessor - extends BaseQueuedPersistedProcessor - implements EthereumListener { - public static readonly persistenceKey = 'L1ToL2TransactionProcessor' - - public static async create( - db: DB, - l1ToL2EventId: string, - listeners: L1ToL2TransactionListener[], - persistenceKey: string = L1ToL2TransactionProcessor.persistenceKey - ): Promise { - const processor = new L1ToL2TransactionProcessor( - db, - l1ToL2EventId, - listeners, - persistenceKey - ) - await processor.init() - return processor - } - - private constructor( - db: DB, - private readonly l1ToL2EventId: string, - private readonly listeners: L1ToL2TransactionListener[], - persistenceKey: string = L1ToL2TransactionProcessor.persistenceKey - ) { - super(db, persistenceKey) - } - - /** - * @inheritDoc - */ - public async handle(event: EthereumEvent): Promise { - if (event.eventID !== this.l1ToL2EventId || !event.values) { - log.debug( - `Received event of wrong ID or with incorrect values. Ignoring event: [${JSON.stringify( - event - )}]` - ) - return - } - - const transaction: L1ToL2Transaction = { - nonce: event.values['_nonce'].toNumber(), - sender: event.values['_sender'], - target: event.values['_target'], - calldata: event.values['_callData'], - } - - await this.add(transaction.nonce, transaction) - } - - /** - * @inheritDoc - */ - public async onSyncCompleted(syncIdentifier?: string): Promise { - // no-op - } - - /** - * @inheritDoc - */ - protected async handleNextItem( - index: number, - item: L1ToL2Transaction - ): Promise { - try { - await Promise.all( - this.listeners.map((x) => x.handleL1ToL2Transaction(item)) - ) - await this.markProcessed(index) - } catch (e) { - this.logError( - `Error processing L1ToL2Transaction in at least one handler. Tx: ${JSON.stringify( - item - )}`, - e - ) - // All errors should be caught in the listeners, so this is fatal. - process.exit(1) - } - } - - /** - * @inheritDoc - */ - protected async serializeItem(item: L1ToL2Transaction): Promise { - return Buffer.from(JSON.stringify(item), 'utf-8') - } - - /** - * @inheritDoc - */ - protected async deserializeItem( - itemBuffer: Buffer - ): Promise { - return JSON.parse(itemBuffer.toString('utf-8')) - } -} diff --git a/packages/rollup-core/src/app/util/environment.ts b/packages/rollup-core/src/app/util/environment.ts index fe54293830f3..0490ce470071 100644 --- a/packages/rollup-core/src/app/util/environment.ts +++ b/packages/rollup-core/src/app/util/environment.ts @@ -94,9 +94,6 @@ export class Environment { public static noL1Node(defaultValue?: boolean) { return !!process.env.NO_L1_NODE || defaultValue } - public static noL1ToL2TransactionProcessor(defaultValue?: boolean) { - return !!process.env.NO_L1_TO_L2_TX_PROCESSOR || defaultValue - } // Local Node Config public static opcodeWhitelistMask( diff --git a/packages/rollup-core/src/types/l1-to-l2-listener.ts b/packages/rollup-core/src/types/l1-to-l2-listener.ts index 407574c43344..9d87cb1da0f1 100644 --- a/packages/rollup-core/src/types/l1-to-l2-listener.ts +++ b/packages/rollup-core/src/types/l1-to-l2-listener.ts @@ -1,20 +1,10 @@ -import { - L1ToL2StateCommitmentBatch, - L1ToL2Transaction, - L1ToL2TransactionBatch, -} from './types' +import { L1ToL2TransactionBatch } from './types' /** - * Defines the event handler interface for L1-to-L2 Transactions. + * Defines the event handler interface for handling L1-to-L2 Transaction batches. */ -export interface L1ToL2TransactionListener { - handleL1ToL2Transaction(transaction: L1ToL2Transaction): Promise -} - export interface L1ToL2TransactionBatchListener { - handleTransactionBatch(batch: L1ToL2TransactionBatch): Promise -} - -export interface L1ToL2StateCommitmentBatchHandler { - handleStateCommitmentBatch(batch: L1ToL2StateCommitmentBatch): Promise + handleL1ToL2TransactionBatch( + transactionBatch: L1ToL2TransactionBatch + ): Promise } diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index 5b1b0d118b93..7448a78d4d0e 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -1,7 +1,7 @@ /* External Imports */ import { BigNumber } from '@eth-optimism/core-utils' -import { TransactionResponse } from 'ethers/providers/abstract-provider' +import { Log, TransactionResponse } from 'ethers/providers/abstract-provider' // TODO: Probably not necessary? // Maybe just a map from token -> contract slot index (e.g. {ETH: 1, BAT: 2, REP: 3})? @@ -27,38 +27,23 @@ export interface L1ToL2Transaction { calldata: string } -export interface TimestampedL1ToL2Transactions { +export interface L1ToL2TransactionBatch { timestamp: number + blockNumber: number transactions: L1ToL2Transaction[] } export type L1ToL2TransactionParser = ( + l: Log, transaction: TransactionResponse ) => Promise + export interface L1ToL2TransactionLogParserContext { topic: string contractAddress: Address parseL2Transactions: L1ToL2TransactionParser } -// TODO: Update when the format is known -export type StateCommitment = string -export interface RollupTransition { - nonce: number - transaction: L1ToL2Transaction - stateCommitment: StateCommitment -} -export interface L1ToL2TransactionBatch { - nonce: number - timestamp: number - transactions: L1ToL2Transaction[] - calldata: string -} -export interface L1ToL2StateCommitmentBatch { - nonce: number - stateCommitments: StateCommitment[] -} - /* Types */ export type Address = string export type StorageSlot = string diff --git a/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts b/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts new file mode 100644 index 000000000000..ebe5cf47391a --- /dev/null +++ b/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts @@ -0,0 +1,372 @@ +import '../setup' + +/* External Imports */ +import { newInMemoryDB } from '@eth-optimism/core-db' +import { add0x, keccak256, sleep, ZERO_ADDRESS } from '@eth-optimism/core-utils' +import * as BigNumber from 'bn.js' + +/* Internal Imports */ +import { + L1ToL2Transaction, + L1ToL2TransactionBatch, + L1ToL2TransactionBatchListener, + L1ToL2TransactionLogParserContext, +} from '../../src/types' +import { CHAIN_ID, L1TransactionBatchProcessor } from '../../src/app' +import { Wallet } from 'ethers' +import { + BaseProvider, + Filter, + JsonRpcProvider, + Log, + TransactionResponse, +} from 'ethers/providers' +import { FilterByBlock } from 'ethers/providers/abstract-provider' + +class DummyListener implements L1ToL2TransactionBatchListener { + public readonly receivedTransactionBatches: L1ToL2TransactionBatch[] = [] + + public async handleL1ToL2TransactionBatch( + transactionBatch: L1ToL2TransactionBatch + ): Promise { + this.receivedTransactionBatches.push(transactionBatch) + } +} + +class MockedProvider extends BaseProvider { + public logsToReturn: Log[] + public transactionsByHash: Map + + constructor() { + super(99) + this.logsToReturn = [] + this.transactionsByHash = new Map() + } + + public async getLogs(filter: Filter | FilterByBlock): Promise { + return this.logsToReturn + } + + public async getTransaction(txHash: string): Promise { + return this.transactionsByHash.get(txHash) + } +} + +const getHashFromString = (s: string): string => { + return add0x(keccak256(Buffer.from(s).toString('hex'))) +} + +const getLog = ( + topics: string[], + address: string, + transactionHash: string = getHashFromString('tx hash'), + logIndex: number = 1, + blockNumber: number = 1, + blockHash: string = getHashFromString('block hash'), + transactionIndex: number = 1 +): Log => { + return { + topics, + transactionHash, + address, + blockNumber, + blockHash, + transactionIndex: 1, + removed: false, + transactionLogIndex: 1, + data: '', + logIndex, + } +} + +const getTransactionResponse = ( + timestamp: number, + data: string, + hash: string, + blockNumber: number = 1, + blockHash: string = getHashFromString('block hash') +): TransactionResponse => { + return { + data, + timestamp, + hash, + blockNumber, + blockHash, + confirmations: 1, + from: ZERO_ADDRESS, + nonce: 1, + gasLimit: undefined, + gasPrice: undefined, + value: undefined, + chainId: CHAIN_ID, + wait: (confirmations) => { + return undefined + }, + } +} + +const getBlock = (timestamp: number, number: number = 1) => { + return { + number, + hash: getHashFromString('derp derp derp'), + parentHash: getHashFromString('parent derp'), + timestamp, + nonce: '0x01', + difficulty: 99999, + gasLimit: undefined, + gasUsed: undefined, + miner: '', + extraData: '', + transactions: [], + } +} + +// add calldata as the key and transactions that are mock-parsed from the calldata as the values +const calldataParseMap: Map = new Map< + string, + L1ToL2Transaction[] +>() +const mapParser = async (l, t) => calldataParseMap.get(t.data) + +const batchTxTopic: string = 'abcd' +const batchTxContractAddress: string = '0xabcdef' +const batchTxLogContext: L1ToL2TransactionLogParserContext = { + topic: batchTxTopic, + contractAddress: batchTxContractAddress, + parseL2Transactions: mapParser, +} + +const singleTxTopic: string = '9999' +const singleTxContractAddress: string = '0x999999' +const singleTxLogContext: L1ToL2TransactionLogParserContext = { + topic: singleTxTopic, + contractAddress: singleTxContractAddress, + parseL2Transactions: mapParser, +} + +const nonce: number = 0 +const sender: string = Wallet.createRandom().address +const target: string = Wallet.createRandom().address +const calldata: string = keccak256(Buffer.from('calldata').toString('hex')) +const l1ToL2Tx: L1ToL2Transaction = { + nonce, + sender, + target, + calldata, +} + +const nonce2: number = 2 +const sender2: string = Wallet.createRandom().address +const target2: string = Wallet.createRandom().address +const calldata2: string = keccak256(Buffer.from('calldata 2').toString('hex')) +const l1ToL2Tx2: L1ToL2Transaction = { + nonce: nonce2, + sender: sender2, + target: target2, + calldata: calldata2, +} + +describe('L1 to L2 Transaction Batch Processor', () => { + let l1ToL2TransactionBatchProcessor: L1TransactionBatchProcessor + let db + let listener: DummyListener + let mockedLogsProvider: MockedProvider + + beforeEach(async () => { + db = newInMemoryDB() + listener = new DummyListener() + mockedLogsProvider = new MockedProvider() + l1ToL2TransactionBatchProcessor = await L1TransactionBatchProcessor.create( + db, + mockedLogsProvider, + [singleTxLogContext, batchTxLogContext], + [listener] + ) + }) + + it('should handle empty block properly', async () => { + await l1ToL2TransactionBatchProcessor.handle({ + number: 1, + hash: getHashFromString('derp derp derp'), + parentHash: getHashFromString('parent derp'), + timestamp: 123, + nonce: '0x01', + difficulty: 99999, + gasLimit: undefined, + gasUsed: undefined, + miner: '', + extraData: '', + transactions: [], + }) + + await sleep(1_000) + + listener.receivedTransactionBatches.length.should.equal( + 0, + 'Should not have received a transaction batch' + ) + }) + + it('should handle block with single log properly', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [l1ToL2Tx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push( + getLog([singleTxTopic], singleTxContractAddress, txHash) + ) + + await l1ToL2TransactionBatchProcessor.handle( + getBlock(timestamp, blockNumber) + ) + + await sleep(1_000) + + listener.receivedTransactionBatches.length.should.equal( + 1, + 'Should have received a transaction batch' + ) + listener.receivedTransactionBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedTransactionBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedTransactionBatches[0].transactions.length.should.equal( + 1, + 'Num transactions mismatch' + ) + listener.receivedTransactionBatches[0].transactions[0].should.eq( + l1ToL2Tx, + 'tx mismatch' + ) + }) + + it('should handle block with multiple logs on same topic properly', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [l1ToL2Tx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push( + getLog([singleTxTopic], singleTxContractAddress, txHash, 1) + ) + + const txHash2 = getHashFromString('derp derp derp2') + calldataParseMap.set(calldata2, [l1ToL2Tx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push( + getLog([singleTxTopic], singleTxContractAddress, txHash2, 2) + ) + + await l1ToL2TransactionBatchProcessor.handle( + getBlock(timestamp, blockNumber) + ) + + await sleep(1_000) + + listener.receivedTransactionBatches.length.should.equal( + 1, + 'Should have received a transaction batch' + ) + listener.receivedTransactionBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedTransactionBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedTransactionBatches[0].transactions.length.should.equal( + 2, + 'Num transactions mismatch' + ) + + listener.receivedTransactionBatches[0].transactions[0].should.eq( + l1ToL2Tx, + 'tx mismatch' + ) + listener.receivedTransactionBatches[0].transactions[1].should.eq( + l1ToL2Tx2, + 'tx2 mismatch' + ) + }) + + it('should handle block with multiple logs on differnt topics properly', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [l1ToL2Tx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push( + getLog([singleTxTopic], singleTxContractAddress, txHash, 1) + ) + + const txHash2 = getHashFromString('derp derp derp2') + calldataParseMap.set(calldata2, [l1ToL2Tx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push( + getLog([batchTxTopic], batchTxContractAddress, txHash2, 2) + ) + + await l1ToL2TransactionBatchProcessor.handle( + getBlock(timestamp, blockNumber) + ) + + await sleep(1_000) + + listener.receivedTransactionBatches.length.should.equal( + 1, + 'Should have received a transaction batch' + ) + listener.receivedTransactionBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedTransactionBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedTransactionBatches[0].transactions.length.should.equal( + 2, + 'Num transactions mismatch' + ) + + listener.receivedTransactionBatches[0].transactions[0].should.eq( + l1ToL2Tx, + 'tx mismatch' + ) + listener.receivedTransactionBatches[0].transactions[1].should.eq( + l1ToL2Tx2, + 'tx2 mismatch' + ) + }) +}) diff --git a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts b/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts deleted file mode 100644 index 99ed7b87e444..000000000000 --- a/packages/rollup-core/test/app/l1-to-l2-transaction-processor.spec.ts +++ /dev/null @@ -1,233 +0,0 @@ -/* External Imports */ -import { newInMemoryDB } from '@eth-optimism/core-db' -import { keccak256, sleep } from '@eth-optimism/core-utils' -import * as BigNumber from 'bn.js' - -/* Internal Imports */ -import { L1ToL2Transaction, L1ToL2TransactionListener } from '../../src/types' -import { - L1ToL2TransactionEventName, - L1ToL2TransactionProcessor, -} from '../../src/app' -import { Wallet } from 'ethers' - -class DummyListener implements L1ToL2TransactionListener { - public readonly receivedTransactions: L1ToL2Transaction[] = [] - - public async handleL1ToL2Transaction( - transaction: L1ToL2Transaction - ): Promise { - this.receivedTransactions.push(transaction) - } -} - -describe('L1 to L2 Transaction Processor', () => { - let l1ToL2TransactionProcessor: L1ToL2TransactionProcessor - let db - let listener: DummyListener - const eventID: string = 'test event id' - - const _nonce: BigNumber = new BigNumber(0) - const _sender: string = Wallet.createRandom().address - const _target: string = Wallet.createRandom().address - const _callData: string = keccak256(Buffer.from('calldata').toString('hex')) - - const nonce2: BigNumber = new BigNumber(1) - const sender2: string = Wallet.createRandom().address - const target2: string = Wallet.createRandom().address - const callData2: string = keccak256(Buffer.from('calldata 2').toString('hex')) - - beforeEach(async () => { - db = newInMemoryDB() - listener = new DummyListener() - l1ToL2TransactionProcessor = await L1ToL2TransactionProcessor.create( - db, - eventID, - [listener] - ) - }) - - it('should handle transaction properly', async () => { - await l1ToL2TransactionProcessor.handle({ - eventID, - name: L1ToL2TransactionEventName, - signature: keccak256(Buffer.from('some random stuff').toString('hex')), - values: { - _nonce, - _sender, - _target, - _callData, - }, - blockNumber: 1, - blockHash: keccak256(Buffer.from('block hash').toString('hex')), - transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), - }) - - await sleep(100) - - listener.receivedTransactions.length.should.equal( - 1, - `Transaction not received!` - ) - listener.receivedTransactions[0].nonce.should.equal( - _nonce.toNumber(), - `Incorrect nonce!` - ) - listener.receivedTransactions[0].sender.should.equal( - _sender, - `Incorrect sender!` - ) - listener.receivedTransactions[0].target.should.equal( - _target, - `Incorrect target!` - ) - listener.receivedTransactions[0].calldata.should.equal( - _callData, - `Incorrect calldata!` - ) - }) - - it('should handle multiple transactions properly', async () => { - await l1ToL2TransactionProcessor.handle({ - eventID, - name: L1ToL2TransactionEventName, - signature: keccak256(Buffer.from('some random stuff').toString('hex')), - values: { - _nonce, - _sender, - _target, - _callData, - }, - blockNumber: 1, - blockHash: keccak256(Buffer.from('block hash').toString('hex')), - transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), - }) - - await l1ToL2TransactionProcessor.handle({ - eventID, - name: L1ToL2TransactionEventName, - signature: keccak256(Buffer.from('some random stuff').toString('hex')), - values: { - _nonce: nonce2, - _sender: sender2, - _target: target2, - _callData: callData2, - }, - blockNumber: 1, - blockHash: keccak256(Buffer.from('block hash').toString('hex')), - transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), - }) - - await sleep(100) - - listener.receivedTransactions.length.should.equal( - 2, - `Transactions not received!` - ) - listener.receivedTransactions[0].nonce.should.equal( - _nonce.toNumber(), - `Incorrect nonce!` - ) - listener.receivedTransactions[0].sender.should.equal( - _sender, - `Incorrect sender!` - ) - listener.receivedTransactions[0].target.should.equal( - _target, - `Incorrect target!` - ) - listener.receivedTransactions[0].calldata.should.equal( - _callData, - `Incorrect calldata!` - ) - - listener.receivedTransactions[1].nonce.should.equal( - nonce2.toNumber(), - `Incorrect nonce 2!` - ) - listener.receivedTransactions[1].sender.should.equal( - sender2, - `Incorrect sender 2!` - ) - listener.receivedTransactions[1].target.should.equal( - target2, - `Incorrect target 2!` - ) - listener.receivedTransactions[1].calldata.should.equal( - callData2, - `Incorrect calldata 2!` - ) - }) - - it('should handle multiple out-of-order transactions properly', async () => { - await l1ToL2TransactionProcessor.handle({ - eventID, - name: L1ToL2TransactionEventName, - signature: keccak256(Buffer.from('some random stuff').toString('hex')), - values: { - _nonce: nonce2, - _sender: sender2, - _target: target2, - _callData: callData2, - }, - blockNumber: 1, - blockHash: keccak256(Buffer.from('block hash').toString('hex')), - transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), - }) - - await l1ToL2TransactionProcessor.handle({ - eventID, - name: L1ToL2TransactionEventName, - signature: keccak256(Buffer.from('some random stuff').toString('hex')), - values: { - _nonce, - _sender, - _target, - _callData, - }, - blockNumber: 1, - blockHash: keccak256(Buffer.from('block hash').toString('hex')), - transactionHash: keccak256(Buffer.from('tx hash').toString('hex')), - }) - - await sleep(100) - - listener.receivedTransactions.length.should.equal( - 2, - `Transactions not received!` - ) - listener.receivedTransactions[0].nonce.should.equal( - _nonce.toNumber(), - `Incorrect nonce!` - ) - listener.receivedTransactions[0].sender.should.equal( - _sender, - `Incorrect sender!` - ) - listener.receivedTransactions[0].target.should.equal( - _target, - `Incorrect target!` - ) - listener.receivedTransactions[0].calldata.should.equal( - _callData, - `Incorrect calldata!` - ) - - listener.receivedTransactions[1].nonce.should.equal( - nonce2.toNumber(), - `Incorrect nonce 2!` - ) - listener.receivedTransactions[1].sender.should.equal( - sender2, - `Incorrect sender 2!` - ) - listener.receivedTransactions[1].target.should.equal( - target2, - `Incorrect target 2!` - ) - listener.receivedTransactions[1].calldata.should.equal( - callData2, - `Incorrect calldata 2!` - ) - }) -}) diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index c579f42fb78c..1d6ab690fdf9 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -7,7 +7,6 @@ import { initializeL2Node, isErrorEVMRevert, L1ToL2Transaction, - L1ToL2TransactionListener, L2NodeContext, L2ToL1Message, } from '@eth-optimism/rollup-core' @@ -63,8 +62,7 @@ for (const eventKey of Object.keys(EMEvents)) { ALL_EXECUTION_MANAGER_EVENT_TOPICS.push(EMEvents[eventKey].topic) } -export class DefaultWeb3Handler - implements Web3Handler, FullnodeHandler, L1ToL2TransactionListener { +export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { private readonly ovmHashToOvmTransactionCache: Object = {} protected blockTimestamps: Object = {} private lock: AsyncLock diff --git a/packages/rollup-full-node/src/exec/fullnode.ts b/packages/rollup-full-node/src/exec/fullnode.ts index a43292645266..697379fa74a5 100644 --- a/packages/rollup-full-node/src/exec/fullnode.ts +++ b/packages/rollup-full-node/src/exec/fullnode.ts @@ -2,7 +2,6 @@ import { BaseDB, DB, - EthereumEventProcessor, getLevelInstance, newInMemoryDB, } from '@eth-optimism/core-db' @@ -18,9 +17,6 @@ import { initializeL1Node, initializeL2Node, L1NodeContext, - L1ToL2TransactionEventName, - L1ToL2TransactionListener, - L1ToL2TransactionProcessor, L2NodeContext, } from '@eth-optimism/rollup-core' import cors = require('cors') @@ -53,7 +49,6 @@ export interface FullnodeContext { fullnodeHandler: FullnodeHandler & Web3Handler fullnodeRpcServer: ExpressHttpServer l2ToL1MessageSubmitter: L2ToL1MessageSubmitter - l1ToL2TransactionProcessor: L1ToL2TransactionProcessor l1NodeContext: L1NodeContext } @@ -144,7 +139,6 @@ const startRoutingServer = async (): Promise => { fullnodeHandler: undefined, fullnodeRpcServer, l2ToL1MessageSubmitter: undefined, - l1ToL2TransactionProcessor: undefined, l1NodeContext: undefined, } } @@ -196,17 +190,6 @@ const startTransactionNode = async ( [cors] ) - let l1ToL2TransactionProcessor: L1ToL2TransactionProcessor - if (!Environment.noL1ToL2TransactionProcessor()) { - l1ToL2TransactionProcessor = await getL1ToL2TransactionProcessor( - testFullnode, - l1NodeContext, - fullnodeHandler - ) - } else { - log.info('Configured to not create an L1ToL2TransactionProcessor.') - } - fullnodeRpcServer.listen() const baseUrl = `http://${Environment.l2RpcServerHost()}:${Environment.l2RpcServerPort()}` @@ -216,7 +199,6 @@ const startTransactionNode = async ( fullnodeHandler, fullnodeRpcServer, l2ToL1MessageSubmitter, - l1ToL2TransactionProcessor, l1NodeContext, } } @@ -271,7 +253,6 @@ const startReadOnlyNode = async ( fullnodeHandler, fullnodeRpcServer, l2ToL1MessageSubmitter: undefined, - l1ToL2TransactionProcessor: undefined, l1NodeContext: undefined, } } @@ -310,47 +291,6 @@ const initializeDBPaths = (isTestMode: boolean) => { } } -/** - * Gets an L1ToL2TransactionProcessor based on configuration and the provided arguments. - * - * Notably this will return undefined if configuration says not to connect to the L1 node. - * - * @param testFullnode Whether or not this is a test full node. - * @param l1NodeContext The L1 node context. - * @param listener The listener to listen to the processor. - * @returns The L1ToL2TransactionProcessor or undefined. - */ -const getL1ToL2TransactionProcessor = async ( - testFullnode: boolean, - l1NodeContext: L1NodeContext, - listener: L1ToL2TransactionListener -): Promise => { - if (Environment.noL1Node()) { - return undefined - } - - const db: DB = getDB(testFullnode) - const l1ToL2TransactionProcessor: L1ToL2TransactionProcessor = await L1ToL2TransactionProcessor.create( - db, - EthereumEventProcessor.getEventID( - l1NodeContext.l1ToL2TransactionPasser.address, - L1ToL2TransactionEventName - ), - [listener] - ) - - const earliestBlock = Environment.l1EarliestBlock() - - const eventProcessor = new EthereumEventProcessor(db, earliestBlock) - await eventProcessor.subscribe( - l1NodeContext.l1ToL2TransactionPasser, - L1ToL2TransactionEventName, - l1ToL2TransactionProcessor - ) - - return l1ToL2TransactionProcessor -} - /** * Updates process environment variables from provided update file * if any variables are updated. diff --git a/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts b/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts deleted file mode 100644 index e4fa6f08b48a..000000000000 --- a/packages/test-ovm-full-node/test/l1-to-l2-transactions.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import './setup' - -/* External Imports */ -import { - runFullnode, - FullnodeContext -} from '@eth-optimism/rollup-full-node' -import {Address, getUnsignedTransactionCalldata} from '@eth-optimism/rollup-core' -import {add0x, getLogger, keccak256, sleep} from '@eth-optimism/core-utils' - -import {Contract, Wallet} from 'ethers' -import {JsonRpcProvider} from 'ethers/providers' -import {deployContract} from 'ethereum-waffle' - -/* Contract Imports */ -import * as SimpleStorage from '../build/SimpleStorage.json' - -const log = getLogger('l1-to-l2-transactions', true) - -const storageKey: string = '0x' + '01'.repeat(32) -const storageValue: string = '0x' + '22'.repeat(32) - -describe.only('L1 To L2 Transaction Passing', () => { - let wallet: Wallet - let simpleStorage: Contract - let provider: JsonRpcProvider - let rollupFullnodeContext: FullnodeContext - - describe('Local tests', () => { - before(async () => { - rollupFullnodeContext = await runFullnode(true) - }) - - after(async () => { - try { - await rollupFullnodeContext.fullnodeRpcServer.close() - } catch (e) { - // don't do anything - } - }) - - beforeEach(async () => { - provider = new JsonRpcProvider('http://0.0.0.0:8545') - wallet = new Wallet(Wallet.createRandom().privateKey, provider) - - simpleStorage = await deployContract(wallet, SimpleStorage, []) - }) - - it('should process l1-to-l2-transaction properly', async () => { - const ovmEntrypoint: Address = simpleStorage.address - const ovmCalldata: string = getUnsignedTransactionCalldata(simpleStorage, 'setStorage', [storageKey, storageValue]) - - await rollupFullnodeContext.l1NodeContext.l1ToL2TransactionPasser.passTransactionToL2(add0x(ovmEntrypoint), ovmCalldata) - - await sleep(8_000) - - const res = await simpleStorage.getStorage(storageKey) - res.should.equal(storageValue, `L1 Transaction did not flow through!`) - }).timeout(20_000) - }) - - // TODO: This is "skip"ped. Remove skip and fill in required vars to test - // note: This sets env vars, so it should not be run in parallel with other tests. - describe.skip('Rinkeby tests!', () => { - before(async () => { - // TODO: Update earliest block here - process.env.L1_EARLIEST_BLOCK = '' - // TODO: Update PK here: - process.env.L1_SEQUENCER_PRIVATE_KEY = '' - process.env.L1_NODE_INFURA_NETWORK = 'rinkeby' - // TODO: Update Infura project ID here: - process.env.L1_NODE_INFURA_PROJECT_ID = '' - // These are the addresses of the contracts on rinkeby - process.env.L1_TO_L2_TRANSACTION_PASSER_ADDRESS = '0xcF8aF92c52245C6595A2de7375F405B24c3a05BD' - process.env.L2_TO_L1_MESSAGE_RECEIVER_ADDRESS = '0x3cD9393742c656c5E33A1a6ee73ef4B27fd54951' - - rollupFullnodeContext = await runFullnode(true) - }) - - after(async () => { - try { - await rollupFullnodeContext.fullnodeRpcServer.close() - } catch (e) { - // don't do anything - } - - delete process.env.L1_EARLIEST_BLOCK - delete process.env.L1_SEQUENCER_PRIVATE_KEY - delete process.env.L1_NODE_INFURA_NETWORK - delete process.env.L1_NODE_INFURA_PROJECT_ID - delete process.env.L1_TO_L2_TRANSACTION_PASSER_ADDRESS - delete process.env.L2_TO_L1_MESSAGE_RECEIVER_ADDRESS - }) - - beforeEach(async () => { - provider = new JsonRpcProvider('http://0.0.0.0:8545') - wallet = new Wallet(Wallet.createRandom().privateKey, provider) - - simpleStorage = await deployContract(wallet, SimpleStorage, []) - }) - - it('should process l1-to-l2-transaction properly', async () => { - const k: number = Math.floor(Math.random() * Math.floor(9007199254740991)) - const v: number = Math.floor(Math.random() * Math.floor(9007199254740991)) - - const randomKey: string = add0x(keccak256(k.toString(16))) - const randomValue: string = add0x(keccak256(v.toString(16))) - - log.info(`Sending L1 message passer contract key ${randomKey} and value: ${randomValue}`) - - const ovmEntrypoint: Address = simpleStorage.address - const ovmCalldata: string = getUnsignedTransactionCalldata(simpleStorage, 'setStorage', [randomKey, randomValue]) - - await rollupFullnodeContext.l1NodeContext.l1ToL2TransactionPasser.passTransactionToL2(add0x(ovmEntrypoint), ovmCalldata) - - log.info(`Waiting 60s for tx to be mined.`) - await sleep(60_000) - - log.info(`Fetching key ${randomKey} from L2 contract.`) - const res = await simpleStorage.getStorage(randomKey) - log.info(`Received value ${res} from L2 contract.`) - res.should.equal(randomValue, `L1 Transaction did not flow through!`) - }).timeout(65_000) - }) - -}) - From 9e8bbcaff9b7744905ac7089910a1b7f12310c59 Mon Sep 17 00:00:00 2001 From: Will Meister Date: Wed, 17 Jun 2020 10:10:52 -0500 Subject: [PATCH 7/9] fix broken empty test package --- packages/l1-to-l2-state-synchronizer/package.json | 9 +-------- packages/l1-to-l2-state-synchronizer/test/setup.ts | 10 ---------- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 packages/l1-to-l2-state-synchronizer/test/setup.ts diff --git a/packages/l1-to-l2-state-synchronizer/package.json b/packages/l1-to-l2-state-synchronizer/package.json index 64347be940bf..430f243ebc5f 100644 --- a/packages/l1-to-l2-state-synchronizer/package.json +++ b/packages/l1-to-l2-state-synchronizer/package.json @@ -9,7 +9,7 @@ ], "scripts": { "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", - "test": "mocha --require ts-node/register 'test/*.spec.ts' --timeout 20000 --exit", + "test": "echo 'no tests here '", "lint": "tslint --format stylish --project .", "fix": "prettier --config ../../prettier-config.json --write '{exec,test}/*.ts'", "build": "mkdir -p ./build && tsc -p .", @@ -33,16 +33,11 @@ "access": "public" }, "devDependencies": { - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.6", "@types/node": "^11.11.3", "@eth-optimism/rollup-full-node": "^0.0.1-alpha.25", - "chai": "^4.2.0", - "chai-bignumber": "^3.0.0", "ethereumjs-abi": "^0.6.8", "lodash": "^4.17.15", "memdown": "^5.0.0", - "mocha": "^6.0.2", "rimraf": "^2.6.3", "typescript": "^3.3.3333" }, @@ -51,8 +46,6 @@ "@eth-optimism/core-utils": "^0.0.1-alpha.25", "@eth-optimism/rollup-core": "^0.0.1-alpha.25", "@eth-optimism/rollup-contracts": "^0.0.1-alpha.25", - "@types/sinon-chai": "^3.2.2", - "chai": "^4.2.0", "ethereum-waffle": "2.1.0", "ethers": "^4.0.37" } diff --git a/packages/l1-to-l2-state-synchronizer/test/setup.ts b/packages/l1-to-l2-state-synchronizer/test/setup.ts deleted file mode 100644 index d92daff79772..000000000000 --- a/packages/l1-to-l2-state-synchronizer/test/setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* External Imports */ -import chai = require('chai') -import bignum = require('chai-bignumber') -import { solidity } from 'ethereum-waffle' - -chai.use(bignum()) -chai.use(solidity) -const should = chai.should() - -export { should } From aefbb3b7b2aaa60794c54545c13cb54f20e5a3c7 Mon Sep 17 00:00:00 2001 From: Will Meister Date: Wed, 17 Jun 2020 17:29:16 -0500 Subject: [PATCH 8/9] Fixing batches to be limited in scope to a single L1 tx, resulting in BlockBatches being a collection of all the batches in a block --- .../app/ethereum/ethereum-block-processor.ts | 6 +- .../exec/l1-to-l2-transaction-synchronizer.ts | 50 +- ...-processor.ts => block-batch-processor.ts} | 89 ++- .../block-batch-submitter.ts | 58 ++ .../src/app/l1-to-l2-event-handling/index.ts | 4 +- .../l2-transaction-batch-submitter.ts | 62 -- .../rollup-core/src/app/util/environment.ts | 17 +- packages/rollup-core/src/types/index.ts | 2 +- .../src/types/l1-to-l2-listener.ts | 10 - packages/rollup-core/src/types/listeners.ts | 8 + packages/rollup-core/src/types/types.ts | 15 +- .../test/app/block-batch-processor.spec.ts | 652 ++++++++++++++++++ .../test/app/block-batch-submitter.spec.ts | 190 +++++ ...-to-l2-transaction-batch-processor.spec.ts | 372 ---------- .../src/app/web3-rpc-handler.ts | 4 +- 15 files changed, 1002 insertions(+), 537 deletions(-) rename packages/rollup-core/src/app/l1-to-l2-event-handling/{l1-transaction-batch-processor.ts => block-batch-processor.ts} (64%) create mode 100644 packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-submitter.ts delete mode 100644 packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts delete mode 100644 packages/rollup-core/src/types/l1-to-l2-listener.ts create mode 100644 packages/rollup-core/src/types/listeners.ts create mode 100644 packages/rollup-core/test/app/block-batch-processor.spec.ts create mode 100644 packages/rollup-core/test/app/block-batch-submitter.spec.ts delete mode 100644 packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts diff --git a/packages/core-db/src/app/ethereum/ethereum-block-processor.ts b/packages/core-db/src/app/ethereum/ethereum-block-processor.ts index 760d8950c4fe..3bc039bf43c5 100644 --- a/packages/core-db/src/app/ethereum/ethereum-block-processor.ts +++ b/packages/core-db/src/app/ethereum/ethereum-block-processor.ts @@ -92,7 +92,7 @@ export class EthereumBlockProcessor { blockNumber: number ): Promise { log.debug(`Fetching block [${blockNumber}].`) - const block: Block = await provider.getBlock(blockNumber, true) + let block: Block = await provider.getBlock(blockNumber, true) log.debug(`Received block: [${JSON.stringify(block)}].`) if ( @@ -121,8 +121,10 @@ export class EthereumBlockProcessor { } log.debug( - `Received ${this.confirmsUntilFinal} confirms for block ${blockNumber}` + `Received ${this.confirmsUntilFinal} confirms for block ${blockNumber}. Refetching block` ) + + block = await provider.getBlock(blockNumber, true) } this.subscriptions.forEach((h) => { diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts index 7b7408b78e38..3fd6652e6f4d 100644 --- a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts +++ b/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts @@ -12,7 +12,7 @@ import { initializeL1Node, L1NodeContext, CHAIN_ID, - L1TransactionBatchProcessor, + BlockBatchProcessor, } from '@eth-optimism/rollup-core' import { JsonRpcProvider, Provider } from 'ethers/providers' @@ -21,12 +21,12 @@ import * as rimraf from 'rimraf' import { Wallet } from 'ethers' import { getWallets } from 'ethereum-waffle' -const log = getLogger('l1-to-l2-transaction-batch-processor') +const log = getLogger('l1-block-batch-processor') export const runTest = async ( l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { +): Promise => { return run(true, l1Provider, l2Provider) } @@ -34,7 +34,7 @@ export const run = async ( testMode: boolean = false, l1Provider?: Provider, l2Provider?: JsonRpcProvider -): Promise => { +): Promise => { initializeDBPaths(testMode) let l1NodeContext: L1NodeContext @@ -52,25 +52,25 @@ export const run = async ( provider = new JsonRpcProvider(Environment.l2NodeWeb3Url(), CHAIN_ID) } - return getL1ToL2TransactionBatchProcessor(testMode, l1NodeContext, provider) + return getL1BlockBatchProcessor(testMode, l1NodeContext, provider) } /** - * Gets an L1ToL2TransactionSynchronizer based on configuration and the provided arguments. + * Gets an BlockBatchProcessor based on configuration and the provided arguments. * * @param testMode Whether or not this is running as a test * @param l1NodeContext The L1 node context. * @param l2Provider The L2 JSON RPC Provider to use to communicate with the L2 node. - * @returns The L1ToL2TransactionSynchronizer. + * @returns The BlockBatchProcessor. */ -const getL1ToL2TransactionBatchProcessor = async ( +const getL1BlockBatchProcessor = async ( testMode: boolean, l1NodeContext: L1NodeContext, l2Provider: JsonRpcProvider -): Promise => { +): Promise => { const db: DB = getDB(testMode) - const stateSynchronizer = await L1TransactionBatchProcessor.create( + const blockBatchProcessor = await BlockBatchProcessor.create( db, l1NodeContext.provider, [], // TODO: fill this in @@ -82,15 +82,15 @@ const getL1ToL2TransactionBatchProcessor = async ( const blockProcessor = new EthereumBlockProcessor( db, earliestBlock, - Environment.stateSynchronizerNumConfirmsRequired() + Environment.blockBatchProcessorNumConfirmsRequired() ) await blockProcessor.subscribe( l1NodeContext.provider, - stateSynchronizer, + blockBatchProcessor, true ) - return stateSynchronizer + return blockBatchProcessor } /** @@ -103,15 +103,15 @@ const getDB = (isTestMode: boolean = false): DB => { if (isTestMode) { return newInMemoryDB() } else { - if (!Environment.stateSynchronizerPersistentDbPath()) { + if (!Environment.blockBatchProcessorPersistentDbPath()) { log.error( - `No L1_TO_L2_STATE_SYNCHRONIZER_PERSISTENT_DB_PATH environment variable present. Please set one!` + `No L1_BLOCK_BATCH_PROCESSOR_PERSISTENT_DB_PATH environment variable present. Please set one!` ) process.exit(1) } return new BaseDB( - getLevelInstance(Environment.stateSynchronizerPersistentDbPath()) + getLevelInstance(Environment.blockBatchProcessorPersistentDbPath()) ) } } @@ -126,13 +126,13 @@ const getDB = (isTestMode: boolean = false): DB => { */ const getL2Wallet = (provider: JsonRpcProvider): Wallet => { let wallet: Wallet - if (!!Environment.stateSynchronizerPrivateKey()) { + if (!!Environment.blockBatchProcessorPrivateKey()) { wallet = new Wallet( - add0x(Environment.stateSynchronizerPrivateKey()), + add0x(Environment.blockBatchProcessorPrivateKey()), provider ) log.info( - `Initialized State Synchronizer wallet from private key. Address: ${wallet.address}` + `Initialized Block Batch Processor wallet from private key. Address: ${wallet.address}` ) } else { wallet = getWallets(provider)[0] @@ -142,11 +142,11 @@ const getL2Wallet = (provider: JsonRpcProvider): Wallet => { } if (!wallet) { - const msg: string = `Wallet not created! Specify the L1_TO_L2_STATE_SYNCHRONIZER_PRIVATE_KEY environment variable to set one!` + const msg: string = `Wallet not created! Specify the L1_BLOCK_BATCH_PROCESSOR_PRIVATE_KEY environment variable to set one!` log.error(msg) throw Error(msg) } else { - log.info(`State Synchronizer wallet created. Address: ${wallet.address}`) + log.info(`Block Batch Processor wallet created. Address: ${wallet.address}`) } return wallet @@ -165,9 +165,9 @@ const initializeDBPaths = (isTestMode: boolean) => { } else { if (Environment.clearDataKey() && !fs.existsSync(getClearDataFilePath())) { log.info(`Detected change in CLEAR_DATA_KEY. Purging data...`) - rimraf.sync(`${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}`) + rimraf.sync(`${Environment.blockBatchProcessorPersistentDbPath()}/{*,.*}`) log.info( - `L2 RPC Server data purged from '${Environment.stateSynchronizerPersistentDbPath()}/{*,.*}'` + `L2 RPC Server data purged from '${Environment.blockBatchProcessorPersistentDbPath()}/{*,.*}'` ) makeDataDirectory() } @@ -178,7 +178,7 @@ const initializeDBPaths = (isTestMode: boolean) => { * Makes the data directory for this full node and adds a clear data key file if it is configured to use one. */ const makeDataDirectory = () => { - fs.mkdirSync(Environment.stateSynchronizerPersistentDbPath(), { + fs.mkdirSync(Environment.blockBatchProcessorPersistentDbPath(), { recursive: true, }) if (Environment.clearDataKey()) { @@ -187,7 +187,7 @@ const makeDataDirectory = () => { } const getClearDataFilePath = () => { - return `${Environment.stateSynchronizerPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` + return `${Environment.blockBatchProcessorPersistentDbPath()}/.clear_data_key_${Environment.clearDataKey()}` } if (typeof require !== 'undefined' && require.main === module) { diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-processor.ts similarity index 64% rename from packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts rename to packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-processor.ts index 71cc77bd6021..6308ac72e001 100644 --- a/packages/rollup-core/src/app/l1-to-l2-event-handling/l1-transaction-batch-processor.ts +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-processor.ts @@ -6,29 +6,30 @@ import { } from '@eth-optimism/core-db' import { getLogger, Logger } from '@eth-optimism/core-utils' +import { Block, Provider, TransactionResponse } from 'ethers/providers' +import { Log } from 'ethers/providers/abstract-provider' + /* Internal Imports */ import { - L1ToL2Transaction, - L1ToL2TransactionBatch, - L1ToL2TransactionBatchListener, - L1ToL2TransactionLogParserContext, + BlockBatches, + BlockBatchListener, + BatchLogParserContext, + L1Batch, } from '../../types' -import { Block, Provider, TransactionResponse } from 'ethers/providers' -import { Log } from 'ethers/providers/abstract-provider' import { addressesAreEqual } from '../utils' -const log: Logger = getLogger('l1-to-l2-transition-synchronizer') +const log: Logger = getLogger('block-batch-processor') -export class L1TransactionBatchProcessor - extends BaseQueuedPersistedProcessor +export class BlockBatchProcessor + extends BaseQueuedPersistedProcessor implements EthereumListener { - public static readonly persistenceKey = 'L1ToL2TransactionSynchronizer' + public static readonly persistenceKey = 'BlockBatchProcessor' private readonly topics: string[] - private readonly topicMap: Map + private readonly topicMap: Map /** - * Creates a L1ToL2TransactionSynchronizer that subscribes to blocks, processes all + * Creates a BlockBatchProcessor that subscribes to blocks, processes all * L1ToL2Transaction events, parses L1ToL2Transactions and submits them to L2. * * @param db The DB to use to persist the queue of L1ToL2Transaction[] objects. @@ -41,11 +42,11 @@ export class L1TransactionBatchProcessor public static async create( db: DB, l1Provider: Provider, - logContexts: L1ToL2TransactionLogParserContext[], - listeners: L1ToL2TransactionBatchListener[], - persistenceKey: string = L1TransactionBatchProcessor.persistenceKey - ): Promise { - const processor = new L1TransactionBatchProcessor( + logContexts: BatchLogParserContext[], + listeners: BlockBatchListener[], + persistenceKey: string = BlockBatchProcessor.persistenceKey + ): Promise { + const processor = new BlockBatchProcessor( db, l1Provider, logContexts, @@ -59,12 +60,12 @@ export class L1TransactionBatchProcessor private constructor( db: DB, private readonly l1Provider: Provider, - logContexts: L1ToL2TransactionLogParserContext[], - private readonly listeners: L1ToL2TransactionBatchListener[], - persistenceKey: string = L1TransactionBatchProcessor.persistenceKey + logContexts: BatchLogParserContext[], + private readonly listeners: BlockBatchListener[], + persistenceKey: string = BlockBatchProcessor.persistenceKey ) { super(db, persistenceKey) - this.topicMap = new Map( + this.topicMap = new Map( logContexts.map((x) => [x.topic, x]) ) this.topics = Array.from(this.topicMap.keys()) @@ -90,28 +91,26 @@ export class L1TransactionBatchProcessor logs.sort((a, b) => a.logIndex - b.logIndex) - const l1ToL2TransactionArrays: L1ToL2Transaction[][] = await Promise.all( - logs.map((l) => this.getTransactionsFromLog(l)) - ) - const transactions: L1ToL2Transaction[] = l1ToL2TransactionArrays.reduce( - (res, curr) => [...res, ...curr], - [] + let batches: L1Batch[] = await Promise.all( + logs.map((l) => this.getBatchFromLog(l)) ) - if (!transactions.length) { + batches = batches.filter((x) => x.length > 0) + + if (!batches.length) { log.debug(`There were no L1toL2Transactions in block ${block.number}.`) } else { log.debug( - `Parsed L1ToL2Transactions from block ${block.number}: ${JSON.stringify( - transactions - )}` + `Parsed ${batches.length} batches from block ${ + block.number + }: ${JSON.stringify(batches)}` ) } this.add(block.number, { blockNumber: block.number, timestamp: block.timestamp, - transactions, + batches, }) } @@ -127,19 +126,17 @@ export class L1TransactionBatchProcessor */ protected async handleNextItem( blockNumber: number, - transactionBatch: L1ToL2TransactionBatch + blockBatches: BlockBatches ): Promise { try { - if (!!transactionBatch.transactions.length) { - this.listeners.map((x) => - x.handleL1ToL2TransactionBatch(transactionBatch) - ) + if (!!blockBatches.batches.length) { + this.listeners.map((x) => x.handleBlockBatches(blockBatches)) } await this.markProcessed(blockNumber) } catch (e) { this.logError( `Error processing L1ToL2Transactions. Txs: ${JSON.stringify( - transactionBatch + blockBatches )}`, e ) @@ -151,20 +148,18 @@ export class L1TransactionBatchProcessor /** * @inheritDoc */ - protected async serializeItem(item: L1ToL2TransactionBatch): Promise { + protected async serializeItem(item: BlockBatches): Promise { return Buffer.from(JSON.stringify(item), 'utf-8') } /** * @inheritDoc */ - protected async deserializeItem( - itemBuffer: Buffer - ): Promise { + protected async deserializeItem(itemBuffer: Buffer): Promise { return JSON.parse(itemBuffer.toString('utf-8')) } - private async getTransactionsFromLog(l: Log): Promise { + private async getBatchFromLog(l: Log): Promise { const matchedTopics: string[] = l.topics.filter( (x) => this.topics.indexOf(x) >= 0 ) @@ -186,16 +181,16 @@ export class L1TransactionBatchProcessor `Fetched tx by hash ${l.transactionHash}: ${JSON.stringify(transaction)}` ) - const parsedTransactions: L1ToL2Transaction[] = [] + const parsedBatch: L1Batch = [] for (const topic of matchedTopics) { const context = this.topicMap.get(topic) if (!addressesAreEqual(l.address, context.contractAddress)) { continue } - const transactions = await context.parseL2Transactions(l, transaction) - parsedTransactions.push(...transactions) + const transactions = await context.parseL1Batch(l, transaction) + parsedBatch.push(...transactions) } - return parsedTransactions + return parsedBatch } } diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-submitter.ts new file mode 100644 index 000000000000..5829f1708119 --- /dev/null +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/block-batch-submitter.ts @@ -0,0 +1,58 @@ +/* External Imports */ +import { getLogger, Logger, numberToHexString } from '@eth-optimism/core-utils' + +import { JsonRpcProvider } from 'ethers/providers' +import { Wallet } from 'ethers' + +/* Internal Imports */ +import { BlockBatches, BlockBatchListener } from '../../types' + +const log: Logger = getLogger('block-batch-submitter') + +export class BlockBatchSubmitter implements BlockBatchListener { + // params: [timestampHex, batchesArrayJSON, signedBatchesArrayJSON] + public static readonly sendBlockBatchesMethod: string = + 'optimism_sendBlockBatches' + + private readonly l2Provider: JsonRpcProvider + + constructor(private readonly l2Wallet: Wallet) { + this.l2Provider = l2Wallet.provider as JsonRpcProvider + } + + /** + * @inheritDoc + */ + public async handleBlockBatches(blockBatches: BlockBatches): Promise { + if (!blockBatches) { + const msg = `Received undefined Block Batch!.` + log.error(msg) + throw msg + } + + if (!blockBatches.batches || !blockBatches.batches.length) { + log.debug(`Moving past empty block ${blockBatches.blockNumber}.`) + return + } + + const timestamp: string = numberToHexString(blockBatches.timestamp) + const txs = JSON.stringify( + blockBatches.batches.map((y) => + y.map((x) => { + return { + nonce: x.nonce >= 0 ? numberToHexString(x.nonce) : undefined, + sender: x.sender, + target: x.target, + calldata: x.calldata, + } + }) + ) + ) + const signedTxsArray: string = await this.l2Wallet.signMessage(txs) + await this.l2Provider.send(BlockBatchSubmitter.sendBlockBatchesMethod, [ + timestamp, + txs, + signedTxsArray, + ]) + } +} diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts index ea8fb711d3fc..9c64bd800fab 100644 --- a/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts +++ b/packages/rollup-core/src/app/l1-to-l2-event-handling/index.ts @@ -1,2 +1,2 @@ -export * from './l2-transaction-batch-submitter' -export * from './l1-transaction-batch-processor' +export * from './block-batch-submitter' +export * from './block-batch-processor' diff --git a/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts b/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts deleted file mode 100644 index 41cb9cbc9376..000000000000 --- a/packages/rollup-core/src/app/l1-to-l2-event-handling/l2-transaction-batch-submitter.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* External Imports */ -import { getLogger, Logger, numberToHexString } from '@eth-optimism/core-utils' - -/* Internal Imports */ -import { - L1ToL2TransactionBatch, - L1ToL2TransactionBatchListener, -} from '../../types' -import { JsonRpcProvider } from 'ethers/providers' -import { Wallet } from 'ethers' - -const log: Logger = getLogger('l1-to-l2-transition-synchronizer') - -// params: [timestampHex, transactionsArrayJSON, signedTransactionsArrayJSON] -const sendL1ToL2TransactionsMethod: string = 'optimism_sendL1ToL2Transactions' - -export class L2TransactionBatchSubmitter - implements L1ToL2TransactionBatchListener { - private readonly l2Provider: JsonRpcProvider - - constructor(private readonly l2Wallet: Wallet) { - this.l2Provider = l2Wallet.provider as JsonRpcProvider - } - - /** - * @inheritDoc - */ - public async handleL1ToL2TransactionBatch( - transactionBatch: L1ToL2TransactionBatch - ): Promise { - if (!transactionBatch) { - const msg = `Received undefined Transaction Batch!.` - log.error(msg) - throw msg - } - - if ( - !transactionBatch.transactions || - !transactionBatch.transactions.length - ) { - log.debug(`Moving past empty block ${transactionBatch.blockNumber}.`) - return - } - - const timestamp: string = numberToHexString(transactionBatch.timestamp) - const txs = JSON.stringify( - transactionBatch.transactions.map((x) => { - return { - nonce: x.nonce > 0 ? numberToHexString(x.nonce) : '', - sender: x.sender, - calldata: x.calldata, - } - }) - ) - const signedTxsArray: string = await this.l2Wallet.signMessage(txs) - await this.l2Provider.send(sendL1ToL2TransactionsMethod, [ - timestamp, - txs, - signedTxsArray, - ]) - } -} diff --git a/packages/rollup-core/src/app/util/environment.ts b/packages/rollup-core/src/app/util/environment.ts index 0490ce470071..30528581cf66 100644 --- a/packages/rollup-core/src/app/util/environment.ts +++ b/packages/rollup-core/src/app/util/environment.ts @@ -180,20 +180,23 @@ export class Environment { : defaultValue } - // State Synchronizer Config - public static stateSynchronizerPersistentDbPath( + // Block Batch Processor Config + public static blockBatchProcessorPersistentDbPath( defaultValue?: string ): string { - return process.env.STATE_SYNCHRONIZER_PERSISTENT_DB_PATH || defaultValue + return ( + process.env.L1_BLOCK_BATCH_PROCESSOR_PERSISTENT_DB_PATH || defaultValue + ) } - public static stateSynchronizerPrivateKey(defaultValue?: string): string { - return process.env.STATE_SYNCHRONIZER_PRIVATE_KEY || defaultValue + public static blockBatchProcessorPrivateKey(defaultValue?: string): string { + return process.env.L1_BLOCK_BATCH_PROCESSOR_PRIVATE_KEY || defaultValue } - public static stateSynchronizerNumConfirmsRequired( + public static blockBatchProcessorNumConfirmsRequired( defaultValue: string = '1' ): number { return parseInt( - process.env.STATE_SYNCHRONIZER_NUM_CONFIRMS_REQUIRED || defaultValue, + process.env.L1_BLOCK_BATCH_PROCESSOR_NUM_CONFIRMS_REQUIRED || + defaultValue, 10 ) } diff --git a/packages/rollup-core/src/types/index.ts b/packages/rollup-core/src/types/index.ts index 718855ef4234..6ca9241617e4 100644 --- a/packages/rollup-core/src/types/index.ts +++ b/packages/rollup-core/src/types/index.ts @@ -1,5 +1,5 @@ export * from './errors' -export * from './l1-to-l2-listener' +export * from './listeners' export * from './node-context' export * from './opcodes' export * from './state-machine' diff --git a/packages/rollup-core/src/types/l1-to-l2-listener.ts b/packages/rollup-core/src/types/l1-to-l2-listener.ts deleted file mode 100644 index 9d87cb1da0f1..000000000000 --- a/packages/rollup-core/src/types/l1-to-l2-listener.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { L1ToL2TransactionBatch } from './types' - -/** - * Defines the event handler interface for handling L1-to-L2 Transaction batches. - */ -export interface L1ToL2TransactionBatchListener { - handleL1ToL2TransactionBatch( - transactionBatch: L1ToL2TransactionBatch - ): Promise -} diff --git a/packages/rollup-core/src/types/listeners.ts b/packages/rollup-core/src/types/listeners.ts new file mode 100644 index 000000000000..35ecafed2271 --- /dev/null +++ b/packages/rollup-core/src/types/listeners.ts @@ -0,0 +1,8 @@ +import { BlockBatches } from './types' + +/** + * Defines the event handler interface for handling L1 Block Batches. + */ +export interface BlockBatchListener { + handleBlockBatches(transactionBatch: BlockBatches): Promise +} diff --git a/packages/rollup-core/src/types/types.ts b/packages/rollup-core/src/types/types.ts index 7448a78d4d0e..4100cd69468d 100644 --- a/packages/rollup-core/src/types/types.ts +++ b/packages/rollup-core/src/types/types.ts @@ -20,28 +20,29 @@ export interface L2ToL1Message { callData: string } -export interface L1ToL2Transaction { +export interface RollupTransaction { nonce: number sender: Address target: Address calldata: string } -export interface L1ToL2TransactionBatch { +export type L1Batch = RollupTransaction[] +export interface BlockBatches { timestamp: number blockNumber: number - transactions: L1ToL2Transaction[] + batches: L1Batch[] } -export type L1ToL2TransactionParser = ( +export type L1BatchParser = ( l: Log, transaction: TransactionResponse -) => Promise +) => Promise -export interface L1ToL2TransactionLogParserContext { +export interface BatchLogParserContext { topic: string contractAddress: Address - parseL2Transactions: L1ToL2TransactionParser + parseL1Batch: L1BatchParser } /* Types */ diff --git a/packages/rollup-core/test/app/block-batch-processor.spec.ts b/packages/rollup-core/test/app/block-batch-processor.spec.ts new file mode 100644 index 000000000000..f4365d887579 --- /dev/null +++ b/packages/rollup-core/test/app/block-batch-processor.spec.ts @@ -0,0 +1,652 @@ +import '../setup' + +/* External Imports */ +import { newInMemoryDB } from '@eth-optimism/core-db' +import { + add0x, + keccak256, + sleep, + TestUtils, + ZERO_ADDRESS, +} from '@eth-optimism/core-utils' + +import { Wallet } from 'ethers' +import { + BaseProvider, + Filter, + Log, + TransactionResponse, +} from 'ethers/providers' +import { FilterByBlock } from 'ethers/providers/abstract-provider' + +/* Internal Imports */ +import { + RollupTransaction, + BlockBatches, + BlockBatchListener, + BatchLogParserContext, + L1Batch, +} from '../../src/types' +import { CHAIN_ID, BlockBatchProcessor } from '../../src/app' + +class DummyListener implements BlockBatchListener { + public readonly receivedBlockBatches: BlockBatches[] = [] + + public async handleBlockBatches(blockBatch: BlockBatches): Promise { + this.receivedBlockBatches.push(blockBatch) + } +} + +class MockedProvider extends BaseProvider { + public logsToReturn: Log[][] + public transactionsByHash: Map + + constructor() { + super(99) + this.logsToReturn = [] + this.transactionsByHash = new Map() + } + + public async getLogs(filter: Filter | FilterByBlock): Promise { + return this.logsToReturn.length > 0 ? this.logsToReturn.pop() : [] + } + + public async getTransaction(txHash: string): Promise { + return this.transactionsByHash.get(txHash) + } +} + +const getHashFromString = (s: string): string => { + return add0x(keccak256(Buffer.from(s).toString('hex'))) +} + +const getLog = ( + topics: string[], + address: string, + transactionHash: string = getHashFromString('tx hash'), + logIndex: number = 1, + blockNumber: number = 1, + blockHash: string = getHashFromString('block hash') +): Log => { + return { + topics, + transactionHash, + address, + blockNumber, + blockHash, + transactionIndex: 1, + removed: false, + transactionLogIndex: 1, + data: '', + logIndex, + } +} + +const getTransactionResponse = ( + timestamp: number, + data: string, + hash: string, + blockNumber: number = 1, + blockHash: string = getHashFromString('block hash') +): TransactionResponse => { + return { + data, + timestamp, + hash, + blockNumber, + blockHash, + confirmations: 1, + from: ZERO_ADDRESS, + nonce: 1, + gasLimit: undefined, + gasPrice: undefined, + value: undefined, + chainId: CHAIN_ID, + wait: (confirmations) => { + return undefined + }, + } +} + +const getBlock = (timestamp: number, number: number = 1) => { + return { + number, + hash: getHashFromString('derp derp derp'), + parentHash: getHashFromString('parent derp'), + timestamp, + nonce: '0x01', + difficulty: 99999, + gasLimit: undefined, + gasUsed: undefined, + miner: '', + extraData: '', + transactions: [], + } +} + +const throwOnParsing: L1Batch = [ + { + nonce: -1, + sender: ZERO_ADDRESS, + target: ZERO_ADDRESS, + calldata: '0xdeadbeef', + }, +] + +// add calldata as the key and transaction batch that is mock-parsed from the calldata as the values +const calldataParseMap: Map = new Map() +const mapParser = async (l, t) => { + const res = calldataParseMap.get(t.data) + if (res === throwOnParsing) { + throw Error('parsing error') + } + return res +} + +const batchTxTopic: string = 'abcd' +const batchTxContractAddress: string = '0xabcdef' +const batchTxLogContext: BatchLogParserContext = { + topic: batchTxTopic, + contractAddress: batchTxContractAddress, + parseL1Batch: mapParser, +} + +const singleTxTopic: string = '9999' +const singleTxContractAddress: string = '0x999999' +const singleTxLogContext: BatchLogParserContext = { + topic: singleTxTopic, + contractAddress: singleTxContractAddress, + parseL1Batch: mapParser, +} + +const nonce: number = 0 +const sender: string = Wallet.createRandom().address +const target: string = Wallet.createRandom().address +const calldata: string = keccak256(Buffer.from('calldata').toString('hex')) +const rollupTx: RollupTransaction = { + nonce, + sender, + target, + calldata, +} + +const nonce2: number = 1 +const sender2: string = Wallet.createRandom().address +const target2: string = Wallet.createRandom().address +const calldata2: string = keccak256(Buffer.from('calldata 2').toString('hex')) +const rollupTx2: RollupTransaction = { + nonce: nonce2, + sender: sender2, + target: target2, + calldata: calldata2, +} + +const rollupTxsEqual = ( + one: RollupTransaction, + two: RollupTransaction +): boolean => { + return JSON.stringify(one) === JSON.stringify(two) +} + +describe('Block Batch Processor', () => { + let blockBatchProcessor: BlockBatchProcessor + let db + let listener: DummyListener + let mockedLogsProvider: MockedProvider + + beforeEach(async () => { + db = newInMemoryDB() + listener = new DummyListener() + mockedLogsProvider = new MockedProvider() + blockBatchProcessor = await BlockBatchProcessor.create( + db, + mockedLogsProvider, + [singleTxLogContext, batchTxLogContext], + [listener] + ) + }) + + describe('positive cases', () => { + it('should handle empty block properly', async () => { + await blockBatchProcessor.handle({ + number: 1, + hash: getHashFromString('derp derp derp'), + parentHash: getHashFromString('parent derp'), + timestamp: 123, + nonce: '0x01', + difficulty: 99999, + gasLimit: undefined, + gasUsed: undefined, + miner: '', + extraData: '', + transactions: [], + }) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 0, + 'Should not have received a transaction batch' + ) + }) + + it('should handle block with single log properly', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 1, + 'Should have received a block batch' + ) + listener.receivedBlockBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedBlockBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedBlockBatches[0].batches.length.should.equal( + 1, + 'Batch count mismatch' + ) + listener.receivedBlockBatches[0].batches[0].length.should.equal( + 1, + 'transaction count mismatch' + ) + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[0][0], + rollupTx + ).should.eq(true, 'tx mismatch') + }) + + it('should handle block with multiple logs on same topic as separate batches', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + const txHash2 = getHashFromString('derp derp derp2') + calldataParseMap.set(calldata2, [rollupTx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash, 1), + getLog([singleTxTopic], singleTxContractAddress, txHash2, 2), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 1, + 'Should have received a block batch' + ) + listener.receivedBlockBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedBlockBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedBlockBatches[0].batches.length.should.equal( + 2, + 'Num batches mismatch' + ) + + listener.receivedBlockBatches[0].batches[0].length.should.equal( + 1, + 'Transaction count mismatch' + ) + listener.receivedBlockBatches[0].batches[1].length.should.equal( + 1, + 'Transaction count mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[0][0], + rollupTx + ).should.eq(true, 'tx mismatch') + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[1][0], + rollupTx2 + ).should.eq(true, 'tx2 mismatch') + }) + + it('should handle block with multiple logs on different topics as separate batches', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + const txHash2 = getHashFromString('derp derp derp2') + calldataParseMap.set(calldata2, [rollupTx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash, 1), + getLog([batchTxTopic], batchTxContractAddress, txHash2, 2), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 1, + 'Should have received a block batche' + ) + listener.receivedBlockBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedBlockBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedBlockBatches[0].batches.length.should.equal( + 2, + 'Num batches mismatch' + ) + + listener.receivedBlockBatches[0].batches[0].length.should.equal( + 1, + 'First batch tx count mismatch' + ) + listener.receivedBlockBatches[0].batches[1].length.should.equal( + 1, + 'Second batch tx count mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[0][0], + rollupTx + ).should.eq(true, 'tx mismatch') + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[1][0], + rollupTx2 + ).should.eq(true, 'tx2 mismatch') + }) + + it('should make different block batches from different blocks', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + const timestamp2 = 1234 + const blockNumber2 = 1 + const txHash2 = getHashFromString('derp derp derp 2') + calldataParseMap.set(calldata2, [rollupTx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp2, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash2), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp2, blockNumber2)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 2, + 'Should have received 2 transaction batches' + ) + + listener.receivedBlockBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedBlockBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedBlockBatches[0].batches.length.should.equal( + 1, + 'Num batches mismatch' + ) + listener.receivedBlockBatches[0].batches[0].length.should.equal( + 1, + 'Num txs mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[0][0], + rollupTx + ).should.eq(true, 'tx mismatch') + + listener.receivedBlockBatches[1].timestamp.should.equal( + timestamp2, + 'Timestamp 2 mismatch' + ) + listener.receivedBlockBatches[1].blockNumber.should.equal( + blockNumber2, + 'Block number 2 mismatch' + ) + listener.receivedBlockBatches[1].batches.length.should.equal( + 1, + 'Num batches 2 mismatch' + ) + listener.receivedBlockBatches[1].batches[0].length.should.equal( + 1, + 'Num transactions 2 mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[1].batches[0][0], + rollupTx2 + ).should.eq(true, 'tx 2 mismatch') + }) + }) + + it('should process batches in order even if blocks are out of order', async () => { + const timestamp2 = 1234 + const blockNumber2 = 1 + const txHash2 = getHashFromString('derp derp derp 2') + calldataParseMap.set(calldata2, [rollupTx2]) + + mockedLogsProvider.transactionsByHash.set( + txHash2, + getTransactionResponse(timestamp2, calldata2, txHash2) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash2), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp2, blockNumber2)) + + await sleep(100) + + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 2, + 'Should have received 2 block batches' + ) + + listener.receivedBlockBatches[0].timestamp.should.equal( + timestamp, + 'Timestamp mismatch' + ) + listener.receivedBlockBatches[0].blockNumber.should.equal( + blockNumber, + 'Block number mismatch' + ) + listener.receivedBlockBatches[0].batches.length.should.equal( + 1, + 'Num batches mismatch' + ) + listener.receivedBlockBatches[0].batches[0].length.should.equal( + 1, + 'Num transactions mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[0].batches[0][0], + rollupTx + ).should.eq(true, 'tx mismatch') + + listener.receivedBlockBatches[1].timestamp.should.equal( + timestamp2, + 'Timestamp 2 mismatch' + ) + listener.receivedBlockBatches[1].blockNumber.should.equal( + blockNumber2, + 'Block number 2 mismatch' + ) + listener.receivedBlockBatches[1].batches.length.should.equal( + 1, + 'Num batches 2 mismatch' + ) + listener.receivedBlockBatches[1].batches[0].length.should.equal( + 1, + 'Num transactions 2 mismatch' + ) + + rollupTxsEqual( + listener.receivedBlockBatches[1].batches[0][0], + rollupTx2 + ).should.eq(true, 'tx 2 mismatch') + }) + + describe('Negative Cases', () => { + it('should not produce a block batch from logs to an incorrect topic', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog(['not the right topic'], singleTxContractAddress, txHash), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 0, + 'Should not have received a block batch' + ) + }) + + it('should not produce a tx batch from logs from an incorrect contract', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, [rollupTx]) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], ZERO_ADDRESS, txHash), + ]) + + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + + await sleep(100) + + listener.receivedBlockBatches.length.should.equal( + 0, + 'Should not have received a transaction batch' + ) + }) + + it('should throw if it cannot correctly parse calldata', async () => { + const timestamp = 123 + const blockNumber = 0 + const txHash = getHashFromString('derp derp derp') + calldataParseMap.set(calldata, throwOnParsing) + + mockedLogsProvider.transactionsByHash.set( + txHash, + getTransactionResponse(timestamp, calldata, txHash) + ) + + mockedLogsProvider.logsToReturn.push([ + getLog([singleTxTopic], singleTxContractAddress, txHash), + ]) + + await TestUtils.assertThrowsAsync(async () => { + await blockBatchProcessor.handle(getBlock(timestamp, blockNumber)) + }) + }) + }) +}) diff --git a/packages/rollup-core/test/app/block-batch-submitter.spec.ts b/packages/rollup-core/test/app/block-batch-submitter.spec.ts new file mode 100644 index 000000000000..c6e83cd6dbfa --- /dev/null +++ b/packages/rollup-core/test/app/block-batch-submitter.spec.ts @@ -0,0 +1,190 @@ +import '../setup' + +/* External Imports */ +import { hexStrToNumber, keccak256, TestUtils } from '@eth-optimism/core-utils' + +import { Wallet } from 'ethers' +import { JsonRpcProvider } from 'ethers/providers' + +/* Internal Imports */ +import { RollupTransaction } from '../../src/types' +import { BlockBatchSubmitter } from '../../src/app' +import { verifyMessage } from 'ethers/utils' + +interface Payload { + method: string + params: any +} + +class MockedProvider extends JsonRpcProvider { + public readonly sent: Payload[] + + constructor() { + super() + this.sent = [] + } + + public async send(method: string, params: any): Promise { + this.sent.push({ method, params }) + return 'dope.' + } +} + +const timestamp: number = 123 +const timestamp2: number = 1234 + +const blockNumber: number = 0 +const blockNumber2: number = 1 + +const nonce: number = 0 +const sender: string = Wallet.createRandom().address +const target: string = Wallet.createRandom().address +const calldata: string = keccak256(Buffer.from('calldata').toString('hex')) +const rollupTx: RollupTransaction = { + nonce, + sender, + target, + calldata, +} + +const nonce2: number = 1 +const sender2: string = Wallet.createRandom().address +const target2: string = Wallet.createRandom().address +const calldata2: string = keccak256(Buffer.from('calldata 2').toString('hex')) +const rollupTx2: RollupTransaction = { + nonce: nonce2, + sender: sender2, + target: target2, + calldata: calldata2, +} + +const rollupTxsEqual = ( + one: RollupTransaction, + two: RollupTransaction +): boolean => { + return JSON.stringify(one) === JSON.stringify(two) +} + +describe('L2 Transaction Batch Submitter', () => { + let blockBatchSubmitter: BlockBatchSubmitter + let mockedSendProvider: MockedProvider + let wallet: Wallet + + beforeEach(async () => { + mockedSendProvider = new MockedProvider() + wallet = Wallet.createRandom().connect(mockedSendProvider) + blockBatchSubmitter = new BlockBatchSubmitter(wallet) + }) + + it('should handle undefined batch properly', async () => { + await TestUtils.assertThrowsAsync(async () => { + await blockBatchSubmitter.handleBlockBatches(undefined) + }) + }) + + it('should handle batch with undefined transactions properly', async () => { + await blockBatchSubmitter.handleBlockBatches({ + timestamp, + blockNumber, + batches: undefined, + }) + + mockedSendProvider.sent.length.should.equal( + 0, + 'Should not have sent anything!' + ) + }) + + it('should handle batch with empty transactions properly', async () => { + await blockBatchSubmitter.handleBlockBatches({ + timestamp, + blockNumber, + batches: [], + }) + + mockedSendProvider.sent.length.should.equal( + 0, + 'Should not have sent anything!' + ) + }) + + it('should send single-tx batch properly', async () => { + await blockBatchSubmitter.handleBlockBatches({ + timestamp, + blockNumber, + batches: [[rollupTx]], + }) + + mockedSendProvider.sent.length.should.equal(1, 'Should have sent tx!') + mockedSendProvider.sent[0].method.should.equal( + BlockBatchSubmitter.sendBlockBatchesMethod, + 'Sent to incorrect Web3 method!' + ) + Array.isArray(mockedSendProvider.sent[0].params).should.equal( + true, + 'Incorrect params type!' + ) + const paramsArray = mockedSendProvider.sent[0].params as string[] + paramsArray.length.should.equal(3, 'Incorrect params length') + const [timestampStr, batchesStr, signature] = paramsArray + + hexStrToNumber(timestampStr).should.equal(timestamp, 'Incorrect timestamp!') + const parsedBatches = JSON.parse(batchesStr) as any[] + parsedBatches.length.should.equal(1, 'Incorrect num batches!') + parsedBatches[0].length.should.equal(1, 'Incorrect num txs!') + parsedBatches[0][0].nonce = hexStrToNumber(parsedBatches[0][0].nonce) + rollupTxsEqual(parsedBatches[0][0], rollupTx).should.equal( + true, + 'Incorrect transaction received!' + ) + + verifyMessage(batchesStr, signature).should.equal( + wallet.address, + 'IncorrectSignature!' + ) + }) + + it('should send multi-tx batch properly', async () => { + await blockBatchSubmitter.handleBlockBatches({ + timestamp, + blockNumber, + batches: [[rollupTx, rollupTx2]], + }) + + mockedSendProvider.sent.length.should.equal(1, 'Should have sent tx!') + mockedSendProvider.sent[0].method.should.equal( + BlockBatchSubmitter.sendBlockBatchesMethod, + 'Sent to incorrect Web3 method!' + ) + Array.isArray(mockedSendProvider.sent[0].params).should.equal( + true, + 'Incorrect params type!' + ) + const paramsArray = mockedSendProvider.sent[0].params as string[] + paramsArray.length.should.equal(3, 'Incorrect params length') + const [timestampStr, txsStr, signature] = paramsArray + + hexStrToNumber(timestampStr).should.equal(timestamp, 'Incorrect timestamp!') + const parsedBatches = JSON.parse(txsStr) as any[] + parsedBatches.length.should.equal(1, 'Incorrect num batches!') + parsedBatches[0].length.should.equal(2, 'Incorrect num transactions!') + parsedBatches[0] = parsedBatches[0].map((x) => { + x.nonce = hexStrToNumber(x.nonce) + return x + }) + rollupTxsEqual(parsedBatches[0][0], rollupTx).should.equal( + true, + 'Incorrect transaction received!' + ) + + rollupTxsEqual(parsedBatches[0][1], rollupTx2).should.equal( + true, + 'Incorrect transaction 2 received!' + ) + + verifyMessage(txsStr, signature).should.equal( + wallet.address, + 'IncorrectSignature!' + ) + }) +}) diff --git a/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts b/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts deleted file mode 100644 index ebe5cf47391a..000000000000 --- a/packages/rollup-core/test/app/l1-to-l2-transaction-batch-processor.spec.ts +++ /dev/null @@ -1,372 +0,0 @@ -import '../setup' - -/* External Imports */ -import { newInMemoryDB } from '@eth-optimism/core-db' -import { add0x, keccak256, sleep, ZERO_ADDRESS } from '@eth-optimism/core-utils' -import * as BigNumber from 'bn.js' - -/* Internal Imports */ -import { - L1ToL2Transaction, - L1ToL2TransactionBatch, - L1ToL2TransactionBatchListener, - L1ToL2TransactionLogParserContext, -} from '../../src/types' -import { CHAIN_ID, L1TransactionBatchProcessor } from '../../src/app' -import { Wallet } from 'ethers' -import { - BaseProvider, - Filter, - JsonRpcProvider, - Log, - TransactionResponse, -} from 'ethers/providers' -import { FilterByBlock } from 'ethers/providers/abstract-provider' - -class DummyListener implements L1ToL2TransactionBatchListener { - public readonly receivedTransactionBatches: L1ToL2TransactionBatch[] = [] - - public async handleL1ToL2TransactionBatch( - transactionBatch: L1ToL2TransactionBatch - ): Promise { - this.receivedTransactionBatches.push(transactionBatch) - } -} - -class MockedProvider extends BaseProvider { - public logsToReturn: Log[] - public transactionsByHash: Map - - constructor() { - super(99) - this.logsToReturn = [] - this.transactionsByHash = new Map() - } - - public async getLogs(filter: Filter | FilterByBlock): Promise { - return this.logsToReturn - } - - public async getTransaction(txHash: string): Promise { - return this.transactionsByHash.get(txHash) - } -} - -const getHashFromString = (s: string): string => { - return add0x(keccak256(Buffer.from(s).toString('hex'))) -} - -const getLog = ( - topics: string[], - address: string, - transactionHash: string = getHashFromString('tx hash'), - logIndex: number = 1, - blockNumber: number = 1, - blockHash: string = getHashFromString('block hash'), - transactionIndex: number = 1 -): Log => { - return { - topics, - transactionHash, - address, - blockNumber, - blockHash, - transactionIndex: 1, - removed: false, - transactionLogIndex: 1, - data: '', - logIndex, - } -} - -const getTransactionResponse = ( - timestamp: number, - data: string, - hash: string, - blockNumber: number = 1, - blockHash: string = getHashFromString('block hash') -): TransactionResponse => { - return { - data, - timestamp, - hash, - blockNumber, - blockHash, - confirmations: 1, - from: ZERO_ADDRESS, - nonce: 1, - gasLimit: undefined, - gasPrice: undefined, - value: undefined, - chainId: CHAIN_ID, - wait: (confirmations) => { - return undefined - }, - } -} - -const getBlock = (timestamp: number, number: number = 1) => { - return { - number, - hash: getHashFromString('derp derp derp'), - parentHash: getHashFromString('parent derp'), - timestamp, - nonce: '0x01', - difficulty: 99999, - gasLimit: undefined, - gasUsed: undefined, - miner: '', - extraData: '', - transactions: [], - } -} - -// add calldata as the key and transactions that are mock-parsed from the calldata as the values -const calldataParseMap: Map = new Map< - string, - L1ToL2Transaction[] ->() -const mapParser = async (l, t) => calldataParseMap.get(t.data) - -const batchTxTopic: string = 'abcd' -const batchTxContractAddress: string = '0xabcdef' -const batchTxLogContext: L1ToL2TransactionLogParserContext = { - topic: batchTxTopic, - contractAddress: batchTxContractAddress, - parseL2Transactions: mapParser, -} - -const singleTxTopic: string = '9999' -const singleTxContractAddress: string = '0x999999' -const singleTxLogContext: L1ToL2TransactionLogParserContext = { - topic: singleTxTopic, - contractAddress: singleTxContractAddress, - parseL2Transactions: mapParser, -} - -const nonce: number = 0 -const sender: string = Wallet.createRandom().address -const target: string = Wallet.createRandom().address -const calldata: string = keccak256(Buffer.from('calldata').toString('hex')) -const l1ToL2Tx: L1ToL2Transaction = { - nonce, - sender, - target, - calldata, -} - -const nonce2: number = 2 -const sender2: string = Wallet.createRandom().address -const target2: string = Wallet.createRandom().address -const calldata2: string = keccak256(Buffer.from('calldata 2').toString('hex')) -const l1ToL2Tx2: L1ToL2Transaction = { - nonce: nonce2, - sender: sender2, - target: target2, - calldata: calldata2, -} - -describe('L1 to L2 Transaction Batch Processor', () => { - let l1ToL2TransactionBatchProcessor: L1TransactionBatchProcessor - let db - let listener: DummyListener - let mockedLogsProvider: MockedProvider - - beforeEach(async () => { - db = newInMemoryDB() - listener = new DummyListener() - mockedLogsProvider = new MockedProvider() - l1ToL2TransactionBatchProcessor = await L1TransactionBatchProcessor.create( - db, - mockedLogsProvider, - [singleTxLogContext, batchTxLogContext], - [listener] - ) - }) - - it('should handle empty block properly', async () => { - await l1ToL2TransactionBatchProcessor.handle({ - number: 1, - hash: getHashFromString('derp derp derp'), - parentHash: getHashFromString('parent derp'), - timestamp: 123, - nonce: '0x01', - difficulty: 99999, - gasLimit: undefined, - gasUsed: undefined, - miner: '', - extraData: '', - transactions: [], - }) - - await sleep(1_000) - - listener.receivedTransactionBatches.length.should.equal( - 0, - 'Should not have received a transaction batch' - ) - }) - - it('should handle block with single log properly', async () => { - const timestamp = 123 - const blockNumber = 0 - const txHash = getHashFromString('derp derp derp') - calldataParseMap.set(calldata, [l1ToL2Tx]) - - mockedLogsProvider.transactionsByHash.set( - txHash, - getTransactionResponse(timestamp, calldata, txHash) - ) - - mockedLogsProvider.logsToReturn.push( - getLog([singleTxTopic], singleTxContractAddress, txHash) - ) - - await l1ToL2TransactionBatchProcessor.handle( - getBlock(timestamp, blockNumber) - ) - - await sleep(1_000) - - listener.receivedTransactionBatches.length.should.equal( - 1, - 'Should have received a transaction batch' - ) - listener.receivedTransactionBatches[0].timestamp.should.equal( - timestamp, - 'Timestamp mismatch' - ) - listener.receivedTransactionBatches[0].blockNumber.should.equal( - blockNumber, - 'Block number mismatch' - ) - listener.receivedTransactionBatches[0].transactions.length.should.equal( - 1, - 'Num transactions mismatch' - ) - listener.receivedTransactionBatches[0].transactions[0].should.eq( - l1ToL2Tx, - 'tx mismatch' - ) - }) - - it('should handle block with multiple logs on same topic properly', async () => { - const timestamp = 123 - const blockNumber = 0 - const txHash = getHashFromString('derp derp derp') - calldataParseMap.set(calldata, [l1ToL2Tx]) - - mockedLogsProvider.transactionsByHash.set( - txHash, - getTransactionResponse(timestamp, calldata, txHash) - ) - - mockedLogsProvider.logsToReturn.push( - getLog([singleTxTopic], singleTxContractAddress, txHash, 1) - ) - - const txHash2 = getHashFromString('derp derp derp2') - calldataParseMap.set(calldata2, [l1ToL2Tx2]) - - mockedLogsProvider.transactionsByHash.set( - txHash2, - getTransactionResponse(timestamp, calldata2, txHash2) - ) - - mockedLogsProvider.logsToReturn.push( - getLog([singleTxTopic], singleTxContractAddress, txHash2, 2) - ) - - await l1ToL2TransactionBatchProcessor.handle( - getBlock(timestamp, blockNumber) - ) - - await sleep(1_000) - - listener.receivedTransactionBatches.length.should.equal( - 1, - 'Should have received a transaction batch' - ) - listener.receivedTransactionBatches[0].timestamp.should.equal( - timestamp, - 'Timestamp mismatch' - ) - listener.receivedTransactionBatches[0].blockNumber.should.equal( - blockNumber, - 'Block number mismatch' - ) - listener.receivedTransactionBatches[0].transactions.length.should.equal( - 2, - 'Num transactions mismatch' - ) - - listener.receivedTransactionBatches[0].transactions[0].should.eq( - l1ToL2Tx, - 'tx mismatch' - ) - listener.receivedTransactionBatches[0].transactions[1].should.eq( - l1ToL2Tx2, - 'tx2 mismatch' - ) - }) - - it('should handle block with multiple logs on differnt topics properly', async () => { - const timestamp = 123 - const blockNumber = 0 - const txHash = getHashFromString('derp derp derp') - calldataParseMap.set(calldata, [l1ToL2Tx]) - - mockedLogsProvider.transactionsByHash.set( - txHash, - getTransactionResponse(timestamp, calldata, txHash) - ) - - mockedLogsProvider.logsToReturn.push( - getLog([singleTxTopic], singleTxContractAddress, txHash, 1) - ) - - const txHash2 = getHashFromString('derp derp derp2') - calldataParseMap.set(calldata2, [l1ToL2Tx2]) - - mockedLogsProvider.transactionsByHash.set( - txHash2, - getTransactionResponse(timestamp, calldata2, txHash2) - ) - - mockedLogsProvider.logsToReturn.push( - getLog([batchTxTopic], batchTxContractAddress, txHash2, 2) - ) - - await l1ToL2TransactionBatchProcessor.handle( - getBlock(timestamp, blockNumber) - ) - - await sleep(1_000) - - listener.receivedTransactionBatches.length.should.equal( - 1, - 'Should have received a transaction batch' - ) - listener.receivedTransactionBatches[0].timestamp.should.equal( - timestamp, - 'Timestamp mismatch' - ) - listener.receivedTransactionBatches[0].blockNumber.should.equal( - blockNumber, - 'Block number mismatch' - ) - listener.receivedTransactionBatches[0].transactions.length.should.equal( - 2, - 'Num transactions mismatch' - ) - - listener.receivedTransactionBatches[0].transactions[0].should.eq( - l1ToL2Tx, - 'tx mismatch' - ) - listener.receivedTransactionBatches[0].transactions[1].should.eq( - l1ToL2Tx2, - 'tx2 mismatch' - ) - }) -}) diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index 1d6ab690fdf9..5bbf00ce822f 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -6,7 +6,7 @@ import { getCurrentTime, initializeL2Node, isErrorEVMRevert, - L1ToL2Transaction, + RollupTransaction, L2NodeContext, L2ToL1Message, } from '@eth-optimism/rollup-core' @@ -814,7 +814,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { * @inheritDoc */ public async handleL1ToL2Transaction( - transaction: L1ToL2Transaction + transaction: RollupTransaction ): Promise { log.debug(`Executing L1 to L2 Transaction ${JSON.stringify(transaction)}`) From 123b8f0f1a979e7970b50c7398fc9eb67ab4aa8b Mon Sep 17 00:00:00 2001 From: Will Meister Date: Thu, 18 Jun 2020 08:47:56 -0500 Subject: [PATCH 9/9] PR feedback --- .github/workflows/build-test-lint.yml | 7 +++++++ .github/workflows/npm_publish.yml | 7 +++++++ packages/l1-to-l2-state-synchronizer/exec/index.ts | 1 - .../testing-contracts/L1ToL2TransactionEvents.sol | 8 ++++---- .../README.md | 0 packages/state-synchronizer/exec/index.ts | 1 + .../exec/rollup-transaction-synchronizer.ts} | 0 .../package.json | 4 ++-- .../tsconfig.json | 0 .../tslint.json | 0 10 files changed, 21 insertions(+), 7 deletions(-) delete mode 100644 packages/l1-to-l2-state-synchronizer/exec/index.ts rename packages/{l1-to-l2-state-synchronizer => state-synchronizer}/README.md (100%) create mode 100644 packages/state-synchronizer/exec/index.ts rename packages/{l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts => state-synchronizer/exec/rollup-transaction-synchronizer.ts} (100%) rename packages/{l1-to-l2-state-synchronizer => state-synchronizer}/package.json (87%) rename packages/{l1-to-l2-state-synchronizer => state-synchronizer}/tsconfig.json (100%) rename packages/{l1-to-l2-state-synchronizer => state-synchronizer}/tslint.json (100%) diff --git a/.github/workflows/build-test-lint.yml b/.github/workflows/build-test-lint.yml index 49660deeadd7..25f996243c1a 100644 --- a/.github/workflows/build-test-lint.yml +++ b/.github/workflows/build-test-lint.yml @@ -97,6 +97,13 @@ jobs: path: packages/solc-transpiler/node_modules key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/solc-transpiler/package.json') }} + - name: Cache state-synchronizer deps + uses: actions/cache@v1 + id: cache_state-synchronizer + with: + path: packages/state-synchronizer/node_modules + key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/state-synchronizer/package.json') }} + - name: Cache ovm-truffle-provider-wrapper deps uses: actions/cache@v1 id: cache_ovm-truffle-provider-wrapper diff --git a/.github/workflows/npm_publish.yml b/.github/workflows/npm_publish.yml index 3e7ae002ce9d..5b93e36acb4e 100644 --- a/.github/workflows/npm_publish.yml +++ b/.github/workflows/npm_publish.yml @@ -86,6 +86,13 @@ jobs: path: packages/solc-transpiler/node_modules key: ${{ runner.os }}-${{ hashFiles('packages/solc-transpiler/package.json') }} + - name: Cache state-synchronizer deps + uses: actions/cache@v1 + id: cache_state-synchronizer + with: + path: packages/state-synchronizer/node_modules + key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/state-synchronizer/package.json') }} + - name: Cache ovm-truffle-provider-wrapper deps uses: actions/cache@v1 id: cache_ovm-truffle-provider-wrapper diff --git a/packages/l1-to-l2-state-synchronizer/exec/index.ts b/packages/l1-to-l2-state-synchronizer/exec/index.ts deleted file mode 100644 index 00cff5cbc9eb..000000000000 --- a/packages/l1-to-l2-state-synchronizer/exec/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './l1-to-l2-transaction-synchronizer' diff --git a/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol b/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol index e76d18b753bb..8434b5898282 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol +++ b/packages/rollup-contracts/contracts/testing-contracts/L1ToL2TransactionEvents.sol @@ -1,13 +1,13 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; -contract L1ToL2TransactionEvents { - event L1ToL2Transaction(); +contract RollupTransactionEvents { + event RollupTransaction(); event SlowQueueTransaction(); event CanonicalTransactionChainBatch(); - function sendL1ToL2Transaction(bytes memory _calldata) public { - emit L1ToL2Transaction(); + function sendRollupTransaction(bytes memory _calldata) public { + emit RollupTransaction(); } function sendSlowQueueTransaction(bytes memory _calldata) public { diff --git a/packages/l1-to-l2-state-synchronizer/README.md b/packages/state-synchronizer/README.md similarity index 100% rename from packages/l1-to-l2-state-synchronizer/README.md rename to packages/state-synchronizer/README.md diff --git a/packages/state-synchronizer/exec/index.ts b/packages/state-synchronizer/exec/index.ts new file mode 100644 index 000000000000..b36b50bf7f7f --- /dev/null +++ b/packages/state-synchronizer/exec/index.ts @@ -0,0 +1 @@ +export * from './rollup-transaction-synchronizer' diff --git a/packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts b/packages/state-synchronizer/exec/rollup-transaction-synchronizer.ts similarity index 100% rename from packages/l1-to-l2-state-synchronizer/exec/l1-to-l2-transaction-synchronizer.ts rename to packages/state-synchronizer/exec/rollup-transaction-synchronizer.ts diff --git a/packages/l1-to-l2-state-synchronizer/package.json b/packages/state-synchronizer/package.json similarity index 87% rename from packages/l1-to-l2-state-synchronizer/package.json rename to packages/state-synchronizer/package.json index 430f243ebc5f..12d0ca123033 100644 --- a/packages/l1-to-l2-state-synchronizer/package.json +++ b/packages/state-synchronizer/package.json @@ -1,7 +1,7 @@ { - "name": "@eth-optimism/l1-to-l2-state-synchronizer", + "name": "@eth-optimism/state-synchronizer", "version": "0.0.1-alpha.25", - "description": "Configurable application for listening for specific L1 state changes and relaying them to L2", + "description": "Configurable application for listening for L1 Rollup Transaction events and relaying the Rollup Transactions to L2", "main": "build/index.js", "files": [ "build/**/*.js", diff --git a/packages/l1-to-l2-state-synchronizer/tsconfig.json b/packages/state-synchronizer/tsconfig.json similarity index 100% rename from packages/l1-to-l2-state-synchronizer/tsconfig.json rename to packages/state-synchronizer/tsconfig.json diff --git a/packages/l1-to-l2-state-synchronizer/tslint.json b/packages/state-synchronizer/tslint.json similarity index 100% rename from packages/l1-to-l2-state-synchronizer/tslint.json rename to packages/state-synchronizer/tslint.json