Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistence Updates #56

Merged
merged 5 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@ services:
ports:
- 8545:8545
environment:
L1_WEB3_URL: http://geth_l1:8545/
L2_WEB3_URL: http://geth_l2:8545/
VOLUME_PATH: /mnt/geth/l2
- OPCODE_WHITELIST_MASK
- L1_SEQUENCER_MNEMONIC
- L2_TO_L1_MESSAGE_RECEIVER_ADDRESS
- L2_TO_L1_MESSAGE_FINALITY_DELAY_IN_BLOCKS
- L2_RPC_SERVER_HOST
- L2_RPC_SERVER_PORT
- L2_WALLET_MNEMONIC
- LOCAL_L1_NODE_PORT
- LOCAL_L2_NODE_PERSISTENT_DB_PATH
- LOCAL_L1_NODE_PERSISTENT_DB_PATH
- L1_NODE_WEB3_URL: http://geth_l1:8545/
- L2_NODE_WEB3_URL: http://geth_l2:8545/
geth_l2:
volumes:
- geth-l2-data:/mnt/geth/l2
Expand Down
16 changes: 8 additions & 8 deletions packages/rollup-full-node/src/app/message-submitter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/* External Imports */
import {
abiEncodeL2ToL1Message,
L2ToL1Message,
} from '@eth-optimism/rollup-core'
import { L2ToL1Message } from '@eth-optimism/rollup-core'
import { getLogger, logError } from '@eth-optimism/core-utils'
import { Contract, Wallet } from 'ethers'

Expand Down Expand Up @@ -30,14 +27,17 @@ export class DefaultL2ToL1MessageSubmitter implements L2ToL1MessageSubmitter {
private highestNonceConfirmed: number

public static async create(
wallet: Wallet,
sequencerWallet: Wallet,
messageReceiverContract: Contract
): Promise<DefaultL2ToL1MessageSubmitter> {
return new DefaultL2ToL1MessageSubmitter(wallet, messageReceiverContract)
return new DefaultL2ToL1MessageSubmitter(
sequencerWallet,
messageReceiverContract
)
}

private constructor(
private readonly wallet: Wallet,
private readonly sequencerWallet: Wallet,
private readonly messageReceiverContract: Contract
) {
this.highestNonceSubmitted = -1
Expand All @@ -54,7 +54,7 @@ export class DefaultL2ToL1MessageSubmitter implements L2ToL1MessageSubmitter {
const callData = this.messageReceiverContract.interface.functions.enqueueL2ToL1Message.encode(
[message]
)
receipt = await this.wallet.sendTransaction({
receipt = await this.sequencerWallet.sendTransaction({
to: this.messageReceiverContract.address,
data: callData,
})
Expand Down
3 changes: 2 additions & 1 deletion packages/rollup-full-node/src/app/test-web3-rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { add0x, getLogger, castToNumber } from '@eth-optimism/core-utils'
import { JsonRpcProvider, Web3Provider } from 'ethers/providers'

/* Internal Imports */
import { initializeL2Node, L2NodeContext } from './index'
import { initializeL2Node } from './index'
import { DefaultWeb3Handler } from './web3-rpc-handler'
import {
L2NodeContext,
L2ToL1MessageSubmitter,
UnsupportedMethodError,
Web3RpcMethods,
Expand Down
59 changes: 59 additions & 0 deletions packages/rollup-full-node/src/app/utils/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { DEFAULT_OPCODE_WHITELIST_MASK } from '@eth-optimism/ovm'

/**
* Class to contain all environment variables referenced by the rollup full node
* to consolidate access / updates and default values.
*/
export class Environment {
// Local Node Config
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good! In the spirit of honing my code style intuitions, I have two observations:

  1. These comments are good because they provide context for the organizational structure of the code, not just what the code does.
  2. We could possibly look for a library which handles config but in this case building this class seems like it wouldn't be much harder than using a library, so it's fine to use a "custom" implementation.

public static opcodeWhitelistMask(
defaultValue: string = DEFAULT_OPCODE_WHITELIST_MASK
): string {
return process.env.OPCODE_WHITELIST_MASK || defaultValue
}
public static localL2NodePersistentDbPath(defaultValue?: string) {
return process.env.LOCAL_L2_NODE_PERSISTENT_DB_PATH || defaultValue
}
public static l2ToL1MessageFinalityDelayInBlocks(
defaultValue: number = 0
): number {
return process.env.L2_TO_L1_MESSAGE_FINALITY_DELAY_IN_BLOCKS
? parseInt(process.env.L2_TO_L1_MESSAGE_FINALITY_DELAY_IN_BLOCKS, 10)
: defaultValue
}
public static localL1NodePersistentDbPath(defaultValue?: string): string {
return process.env.LOCAL_L1_NODE_PERSISTENT_DB_PATH || defaultValue
}

// L2 Config
public static l2RpcServerHost(defaultValue: string = '0.0.0.0'): string {
return process.env.L2_RPC_SERVER_HOST || defaultValue
}
public static l2RpcServerPort(defaultValue: number = 8545): number {
return process.env.L2_RPC_SERVER_PORT
? parseInt(process.env.L2_RPC_SERVER_PORT, 10)
: defaultValue
}
public static l2NodeWeb3Url(defaultValue?: string): string {
return process.env.L2_NODE_WEB3_URL || defaultValue
}
public static l2WalletMnemonic(defaultValue?: string): string {
return process.env.L2_WALLET_MNEMONIC || defaultValue
}

// L1 Config
public static l1NodeWeb3Url(defaultValue?: string): string {
return process.env.L1_NODE_WEB3_URL || defaultValue
}
public static localL1NodePort(defaultValue: number = 7545): number {
return process.env.LOCAL_L1_NODE_PORT
? parseInt(process.env.LOCAL_L1_NODE_PORT, 10)
: defaultValue
}
public static sequencerMnemonic(defaultValue?: string): string {
return process.env.L1_SEQUENCER_MNEMONIC || defaultValue
}
public static l2ToL1MessageReceiverAddress(defaultValue?: string): string {
return process.env.L2_TO_L1_MESSAGE_RECEIVER_ADDRESS || defaultValue
}
}
1 change: 1 addition & 0 deletions packages/rollup-full-node/src/app/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './environment'
export * from './local-l1-node'
export * from './l2-node'
export * from './utils'
54 changes: 24 additions & 30 deletions packages/rollup-full-node/src/app/utils/l2-node.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,39 @@
/* Externals Import */
import {
add0x,
getDeployedContractAddress,
getLogger,
} from '@eth-optimism/core-utils'
import { getDeployedContractAddress, getLogger } from '@eth-optimism/core-utils'
import {
GAS_LIMIT,
L2ExecutionManagerContractDefinition,
L2ToL1MessagePasserContractDefinition,
DEFAULT_OPCODE_WHITELIST_MASK,
CHAIN_ID,
L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS,
} from '@eth-optimism/ovm'
import { Address } from '@eth-optimism/rollup-core'

import { Contract, Wallet } from 'ethers'
import { createMockProvider, getWallets } from 'ethereum-waffle'
import { readFile } from 'fs'

/* Internal Imports */
import { DEFAULT_ETHNODE_GAS_LIMIT, deployContract } from '../index'
import {
DEFAULT_ETHNODE_GAS_LIMIT,
deployContract,
Environment,
} from '../index'
import { JsonRpcProvider } from 'ethers/providers'
import { promisify } from 'util'
import { L2NodeContext } from '../../types'

const readFileAsync = promisify(readFile)
const log = getLogger('l2-node')

/* Configuration */
const opcodeWhitelistMask: string =
process.env.OPCODE_WHITELIST_MASK || DEFAULT_OPCODE_WHITELIST_MASK
const volumePath: string = process.env.VOLUME_PATH || '/'
const privateKeyFilePath: string =
process.env.PRIVATE_KEY_FILE_PATH || volumePath + '/private_key.txt'

export interface L2NodeContext {
provider: JsonRpcProvider
wallet: Wallet
executionManager: Contract
l2ToL1MessagePasser: Contract
}

export const getPersistedL2GanacheDbPath = () =>
process.env.PERSISTED_L2_GANACHE_DB_FILE_PATH

/**
* Initializes the L2 Node, which entails:
* - Creating a local instance if the given provider is undefined.
* - Creating a wallet to use to interact with it.
* - Deploying the ExecutionManager if it does not already exist.
*
* @param web3Provider (optional) The JsonRpcProvider to use to connect to a remote node.
* @returns The L2NodeContext necessary to interact with the L2 Node.
*/
export async function initializeL2Node(
web3Provider?: JsonRpcProvider
): Promise<L2NodeContext> {
Expand All @@ -52,7 +43,7 @@ export async function initializeL2Node(
gasLimit: DEFAULT_ETHNODE_GAS_LIMIT,
allowUnlimitedContractSize: true,
}
const persistedGanacheDbPath = getPersistedL2GanacheDbPath()
const persistedGanacheDbPath = Environment.localL2NodePersistentDbPath()
if (!!persistedGanacheDbPath) {
opts['db_path'] = persistedGanacheDbPath
opts['network_id'] = CHAIN_ID
Expand All @@ -61,12 +52,15 @@ export async function initializeL2Node(
}

// Initialize a fullnode for us to interact with
let wallet
let wallet: Wallet

// If we're given a provider, our wallet must be configured from a private key file
if (web3Provider) {
const privateKey: string = await readFileAsync(privateKeyFilePath, 'utf8')
wallet = new Wallet(add0x(privateKey), provider)
wallet = !!Environment.l2WalletMnemonic()
? Wallet.fromMnemonic(Environment.l2WalletMnemonic())
: Wallet.createRandom()

wallet.connect(provider)
} else {
;[wallet] = getWallets(provider)
}
Expand Down Expand Up @@ -125,7 +119,7 @@ export async function deployExecutionManager(
const executionManager: Contract = await deployContract(
wallet,
L2ExecutionManagerContractDefinition,
[opcodeWhitelistMask, wallet.address, GAS_LIMIT, true],
[Environment.opcodeWhitelistMask(), wallet.address, GAS_LIMIT, true],
{ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }
)

Expand Down
34 changes: 23 additions & 11 deletions packages/rollup-full-node/src/app/utils/local-l1-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,43 @@ import { Contract, ethers, providers, Wallet } from 'ethers'
import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle'

/* Internal Imports */
import { DEFAULT_ETHNODE_GAS_LIMIT } from '../index'
import { DEFAULT_ETHNODE_GAS_LIMIT, Environment } from '../index'
import { L1NodeContext } from '../../types'

const finalityDelayInBlocks: string =
process.env.FINALITY_DELAY_IN_BLOCKS || '0'
const l1NodeLevelDBPath: string = process.env.L1_NODE_LEVELDB_PATH
/**
* Starts a local node on the provided port, using the provided mnemonic to
* deploy the necessary contracts for bootstrapping.
*
* @param sequencerMnemonic The mnemonic to use for the Sequencer in contracts that need Sequencer ownership or reference.
* @param port The port the node should be reachable at.
* @returns The L1 node context with all info necessary to use the L1 node.
*/
export const startLocalL1Node = async (
Copy link
Contributor

@karlfloersch karlfloersch Mar 25, 2020

Choose a reason for hiding this comment

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

Hmm the terminology here is a little confusing. Am I getting it right that "LocalL1Node" is what we send "InternalTransactions" to? It's just really confusing and I want to make sure we have all of the components mapped out with reasonable terminology.

I just made a diagram which illustrates all of the components.

IMG_6A835E8216F2-1

So to be clear, L1 Node would be just a normal Geth instance that's syncing mainnet Ethereum. Then an L2 Node or maybe more specifically Rollup Node would be the thing that runs an Internal instance of Geth / EVM, which itself either emulates an OVM deployed to it (ExecutionManger, etc).

Does that match up with the terminology being used here?

Copy link
Author

Choose a reason for hiding this comment

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

Yep, that's it! The line in question is to specifically start an L1 node locally to fully exercise the end-to-end functionality of L2 <--> L1 messaging. This is completely different than the L2 Node. I went with L1 and L2 because nothing is actually "rolled up" in the L2 node, and instead, the rollup state sits in the L1 node. Wanted to avoid that confusion. Does L1 and L2 sound good? Happy to rename it to rollup if you think it makes more sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah great! So I think after staring at this for a while I see where my confusion was. The fact that inside the LocalL1Node we have a sequencerWallet was throwing me off. But it’s just because in this circumstance the L1 that we are running is a Local testnet run by the sequencer.

These L1 & L2 terms are a little confusing! Especially when you see L1ToL2 right next to L2ToL1 — my dyslexia goes crazy! That said, it’s not clear to me that EthNode and RollupNode (for example) are much better 😕 Especially because both are in some sense “Eth” nodes.

I realize this could require some refactoring/renaming (and so I’m happy to not do it) but I wonder if instead of L1ToL2/L2ToL1, we could use the terms L2InboundMessages and L2OutboundMessages or something? Just spitballing and curious what you / @ben-chain think.

It’s a nitpick though & might be specific to my difficulty reading 😂

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh nvm I take it back. I’m now thinking about l2ToL1MessageReceiver and calling it l2OutboundMessageReceiver and realize that these variable names are going to be confusing basically no matter how we slice it.

Unless y'all absolutely love my naming change (which might have been closer to what Ben did originally?) doesn’t feel worth the switching cost.

mnemonic: string,
port: number
): Promise<providers.Web3Provider> => {
): Promise<L1NodeContext> => {
const opts = {
gasLimit: DEFAULT_ETHNODE_GAS_LIMIT,
allowUnlimitedContractSize: true,
locked: false,
port,
mnemonic,
}
if (!!l1NodeLevelDBPath) {
opts['db_path'] = l1NodeLevelDBPath
if (!!Environment.localL1NodePersistentDbPath()) {
opts['db_path'] = Environment.localL1NodePersistentDbPath()
}
const provider: providers.Web3Provider = createMockProvider(opts)

const wallet = getWallets(provider)[0]
await deployL2ToL1MessageReceiver(wallet)
const sequencerWallet = getWallets(provider)[0]
const l2ToL1MessageReceiver = await deployL2ToL1MessageReceiver(
sequencerWallet
)

return provider
return {
provider,
sequencerWallet,
l2ToL1MessageReceiver,
}
}

export const deployL2ToL1MessageReceiver = async (
Expand All @@ -38,7 +50,7 @@ export const deployL2ToL1MessageReceiver = async (
const contract = await deployContract(
wallet,
L2ToL1MessageReceiverContractDefinition,
[wallet.address, parseInt(finalityDelayInBlocks, 10)]
[wallet.address, Environment.l2ToL1MessageFinalityDelayInBlocks()]
)

process.env.L2_TO_L1_MESSAGE_RECEIVER_ADDRESS = contract.address
Expand Down
3 changes: 2 additions & 1 deletion packages/rollup-full-node/src/app/web3-rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import AsyncLock from 'async-lock'
import {
FullnodeHandler,
InvalidParametersError,
L2NodeContext,
L2ToL1MessageSubmitter,
RevertError,
UnsupportedMethodError,
Web3Handler,
Web3RpcMethods,
} from '../types'
import { initializeL2Node, L2NodeContext } from './utils'
import { initializeL2Node } from './utils'
import { NoOpL2ToL1MessageSubmitter } from './message-submitter'

const log = getLogger('web3-handler')
Expand Down
Loading