Commit 5968bada authored by smartcontracts's avatar smartcontracts Committed by GitHub

Merge pull request #1939 from ethereum-optimism/develop

Develop -> Master
parents 8e9834b3 654a4907
---
'@eth-optimism/proxyd': patch
---
Force proxyd build
---
'@eth-optimism/l2geth': patch
---
expose ErrNonceTooHigh from miner
...@@ -119,8 +119,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) { ...@@ -119,8 +119,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
log.Root().SetHandler(log.LvlFilterHandler(logLevel, logHandler)) log.Root().SetHandler(log.LvlFilterHandler(logLevel, logHandler))
log.Info("Config", "config", fmt.Sprintf("%#v", cfg))
// Parse sequencer private key and CTC contract address. // Parse sequencer private key and CTC contract address.
sequencerPrivKey, ctcAddress, err := parseWalletPrivKeyAndContractAddr( sequencerPrivKey, ctcAddress, err := parseWalletPrivKeyAndContractAddr(
"Sequencer", cfg.Mnemonic, cfg.SequencerHDPath, "Sequencer", cfg.Mnemonic, cfg.SequencerHDPath,
...@@ -171,7 +169,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) { ...@@ -171,7 +169,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
var batchTxService *Service var batchTxService *Service
if cfg.RunTxBatchSubmitter { if cfg.RunTxBatchSubmitter {
batchTxDriver, err := sequencer.NewDriver(sequencer.Config{ batchTxDriver, err := sequencer.NewDriver(sequencer.Config{
Name: "SEQUENCER", Name: "Sequencer",
L1Client: l1Client, L1Client: l1Client,
L2Client: l2Client, L2Client: l2Client,
BlockOffset: cfg.BlockOffset, BlockOffset: cfg.BlockOffset,
...@@ -196,7 +194,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) { ...@@ -196,7 +194,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
var batchStateService *Service var batchStateService *Service
if cfg.RunStateBatchSubmitter { if cfg.RunStateBatchSubmitter {
batchStateDriver, err := proposer.NewDriver(proposer.Config{ batchStateDriver, err := proposer.NewDriver(proposer.Config{
Name: "PROPOSER", Name: "Proposer",
L1Client: l1Client, L1Client: l1Client,
L2Client: l2Client, L2Client: l2Client,
BlockOffset: cfg.BlockOffset, BlockOffset: cfg.BlockOffset,
......
...@@ -5,9 +5,11 @@ import ( ...@@ -5,9 +5,11 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/big" "math/big"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc" "github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/scc" "github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/scc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
l2types "github.com/ethereum-optimism/optimism/l2geth/core/types" l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
...@@ -36,6 +38,7 @@ type Driver struct { ...@@ -36,6 +38,7 @@ type Driver struct {
sccContract *scc.StateCommitmentChain sccContract *scc.StateCommitmentChain
ctcContract *ctc.CanonicalTransactionChain ctcContract *ctc.CanonicalTransactionChain
walletAddr common.Address walletAddr common.Address
metrics *metrics.Metrics
} }
func NewDriver(cfg Config) (*Driver, error) { func NewDriver(cfg Config) (*Driver, error) {
...@@ -60,6 +63,7 @@ func NewDriver(cfg Config) (*Driver, error) { ...@@ -60,6 +63,7 @@ func NewDriver(cfg Config) (*Driver, error) {
sccContract: sccContract, sccContract: sccContract,
ctcContract: ctcContract, ctcContract: ctcContract,
walletAddr: walletAddr, walletAddr: walletAddr,
metrics: metrics.NewMetrics(cfg.Name),
}, nil }, nil
} }
...@@ -73,6 +77,11 @@ func (d *Driver) WalletAddr() common.Address { ...@@ -73,6 +77,11 @@ func (d *Driver) WalletAddr() common.Address {
return d.walletAddr return d.walletAddr
} }
// Metrics returns the subservice telemetry object.
func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
// GetBatchBlockRange returns the start and end L2 block heights that need to be // GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned // processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed. // values are identical nothing needs to be processed.
...@@ -121,6 +130,8 @@ func (d *Driver) SubmitBatchTx( ...@@ -121,6 +130,8 @@ func (d *Driver) SubmitBatchTx(
ctx context.Context, ctx context.Context,
start, end, nonce, gasPrice *big.Int) (*types.Transaction, error) { start, end, nonce, gasPrice *big.Int) (*types.Transaction, error) {
batchTxBuildStart := time.Now()
var blocks []*l2types.Block var blocks []*l2types.Block
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) { for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
block, err := d.cfg.L2Client.BlockByNumber(ctx, i) block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
...@@ -139,6 +150,10 @@ func (d *Driver) SubmitBatchTx( ...@@ -139,6 +150,10 @@ func (d *Driver) SubmitBatchTx(
stateRoots = append(stateRoots, block.Root()) stateRoots = append(stateRoots, block.Root())
} }
batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
opts, err := bind.NewKeyedTransactorWithChainID( opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID, d.cfg.PrivKey, d.cfg.ChainID,
) )
......
...@@ -7,8 +7,10 @@ import ( ...@@ -7,8 +7,10 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"strings" "strings"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc" "github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
l2types "github.com/ethereum-optimism/optimism/l2geth/core/types" l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
...@@ -43,6 +45,7 @@ type Driver struct { ...@@ -43,6 +45,7 @@ type Driver struct {
rawCtcContract *bind.BoundContract rawCtcContract *bind.BoundContract
walletAddr common.Address walletAddr common.Address
ctcABI *abi.ABI ctcABI *abi.ABI
metrics *metrics.Metrics
} }
func NewDriver(cfg Config) (*Driver, error) { func NewDriver(cfg Config) (*Driver, error) {
...@@ -78,6 +81,7 @@ func NewDriver(cfg Config) (*Driver, error) { ...@@ -78,6 +81,7 @@ func NewDriver(cfg Config) (*Driver, error) {
rawCtcContract: rawCtcContract, rawCtcContract: rawCtcContract,
walletAddr: walletAddr, walletAddr: walletAddr,
ctcABI: ctcABI, ctcABI: ctcABI,
metrics: metrics.NewMetrics(cfg.Name),
}, nil }, nil
} }
...@@ -91,6 +95,11 @@ func (d *Driver) WalletAddr() common.Address { ...@@ -91,6 +95,11 @@ func (d *Driver) WalletAddr() common.Address {
return d.walletAddr return d.walletAddr
} }
// Metrics returns the subservice telemetry object.
func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
// GetBatchBlockRange returns the start and end L2 block heights that need to be // GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned // processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed. // values are identical nothing needs to be processed.
...@@ -136,6 +145,8 @@ func (d *Driver) SubmitBatchTx( ...@@ -136,6 +145,8 @@ func (d *Driver) SubmitBatchTx(
log.Info(name+" submitting batch tx", "start", start, "end", end, log.Info(name+" submitting batch tx", "start", start, "end", end,
"gasPrice", gasPrice) "gasPrice", gasPrice)
batchTxBuildStart := time.Now()
var blocks []*l2types.Block var blocks []*l2types.Block
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) { for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
block, err := d.cfg.L2Client.BlockByNumber(ctx, i) block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
...@@ -176,6 +187,11 @@ func (d *Driver) SubmitBatchTx( ...@@ -176,6 +187,11 @@ func (d *Driver) SubmitBatchTx(
panic("call data too large") panic("call data too large")
} }
// Record the batch_tx_build_time.
batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
log.Info(name+" batch call data", "data", hex.EncodeToString(batchCallData)) log.Info(name+" batch call data", "data", hex.EncodeToString(batchCallData))
opts, err := bind.NewKeyedTransactorWithChainID( opts, err := bind.NewKeyedTransactorWithChainID(
......
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type Metrics struct {
// ETHBalance tracks the amount of ETH in the submitter's account.
ETHBalance prometheus.Gauge
// BatchSizeInBytes tracks the size of batch submission transactions.
BatchSizeInBytes prometheus.Histogram
// NumTxPerBatch tracks the number of L2 transactions in each batch
// submission.
NumTxPerBatch prometheus.Histogram
// SubmissionGasUsed tracks the amount of gas used to submit each batch.
SubmissionGasUsed prometheus.Histogram
// BatchsSubmitted tracks the total number of successful batch submissions.
BatchesSubmitted prometheus.Counter
// FailedSubmissions tracks the total number of failed batch submissions.
FailedSubmissions prometheus.Counter
// BatchTxBuildTime tracks the duration it takes to construct a batch
// transaction.
BatchTxBuildTime prometheus.Gauge
// BatchConfirmationTime tracks the duration it takes to confirm a batch
// transaction.
BatchConfirmationTime prometheus.Gauge
}
func NewMetrics(subsystem string) *Metrics {
return &Metrics{
ETHBalance: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_eth_balance",
Help: "ETH balance of the batch submitter",
Subsystem: subsystem,
}),
BatchSizeInBytes: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_batch_size_in_bytes",
Help: "Size of batches in bytes",
Subsystem: subsystem,
}),
NumTxPerBatch: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_num_txs_per_batch",
Help: "Number of transaction in each batch",
Subsystem: subsystem,
}),
SubmissionGasUsed: promauto.NewHistogram(prometheus.HistogramOpts{
Name: "batch_submitter_submission_gas_used",
Help: "Gas used to submit each batch",
Subsystem: subsystem,
}),
BatchesSubmitted: promauto.NewCounter(prometheus.CounterOpts{
Name: "batch_submitter_batches_submitted",
Help: "Count of batches submitted",
Subsystem: subsystem,
}),
FailedSubmissions: promauto.NewCounter(prometheus.CounterOpts{
Name: "batch_submitter_failed_submissions",
Help: "Count of failed batch submissions",
Subsystem: subsystem,
}),
BatchTxBuildTime: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_batch_tx_build_time",
Help: "Time to construct batch transactions",
Subsystem: subsystem,
}),
BatchConfirmationTime: promauto.NewGauge(prometheus.GaugeOpts{
Name: "batch_submitter_batch_confirmation_time",
Help: "Time to confirm batch transactions",
Subsystem: subsystem,
}),
}
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -13,6 +14,11 @@ import ( ...@@ -13,6 +14,11 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
var (
// weiToGwei is the conversion rate from wei to gwei.
weiToGwei = new(big.Float).SetFloat64(1e-18)
)
// Driver is an interface for creating and submitting batch transactions for a // Driver is an interface for creating and submitting batch transactions for a
// specific contract. // specific contract.
type Driver interface { type Driver interface {
...@@ -23,6 +29,9 @@ type Driver interface { ...@@ -23,6 +29,9 @@ type Driver interface {
// fees. // fees.
WalletAddr() common.Address WalletAddr() common.Address
// Metrics returns the subservice telemetry object.
Metrics() *metrics.Metrics
// GetBatchBlockRange returns the start and end L2 block heights that // GetBatchBlockRange returns the start and end L2 block heights that
// need to be processed. Note that the end value is *exclusive*, // need to be processed. Note that the end value is *exclusive*,
// therefore if the returned values are identical nothing needs to be // therefore if the returned values are identical nothing needs to be
...@@ -51,7 +60,8 @@ type Service struct { ...@@ -51,7 +60,8 @@ type Service struct {
ctx context.Context ctx context.Context
cancel func() cancel func()
txMgr txmgr.TxManager txMgr txmgr.TxManager
metrics *metrics.Metrics
wg sync.WaitGroup wg sync.WaitGroup
} }
...@@ -64,10 +74,11 @@ func NewService(cfg ServiceConfig) *Service { ...@@ -64,10 +74,11 @@ func NewService(cfg ServiceConfig) *Service {
) )
return &Service{ return &Service{
cfg: cfg, cfg: cfg,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
txMgr: txMgr, txMgr: txMgr,
metrics: cfg.Driver.Metrics(),
} }
} }
...@@ -91,21 +102,35 @@ func (s *Service) eventLoop() { ...@@ -91,21 +102,35 @@ func (s *Service) eventLoop() {
for { for {
select { select {
case <-time.After(s.cfg.PollInterval): case <-time.After(s.cfg.PollInterval):
log.Info(name + " fetching current block range") // Record the submitter's current ETH balance. This is done first in
// case any of the remaining steps fail, we can at least have an
// accurate view of the submitter's balance.
balance, err := s.cfg.L1Client.BalanceAt(
s.ctx, s.cfg.Driver.WalletAddr(), nil,
)
if err != nil {
log.Error(name+" unable to get current balance", "err", err)
continue
}
s.metrics.ETHBalance.Set(weiToGwei64(balance))
// Determine the range of L2 blocks that the batch submitter has not
// processed, and needs to take action on.
log.Info(name + " fetching current block range")
start, end, err := s.cfg.Driver.GetBatchBlockRange(s.ctx) start, end, err := s.cfg.Driver.GetBatchBlockRange(s.ctx)
if err != nil { if err != nil {
log.Error(name+" unable to get block range", "err", err) log.Error(name+" unable to get block range", "err", err)
continue continue
} }
log.Info(name+" block range", "start", start, "end", end)
// No new updates. // No new updates.
if start.Cmp(end) == 0 { if start.Cmp(end) == 0 {
log.Info(name+" no updates", "start", start, "end", end)
continue continue
} }
log.Info(name+" block range", "start", start, "end", end)
// Query for the submitter's current nonce.
nonce64, err := s.cfg.L1Client.NonceAt( nonce64, err := s.cfg.L1Client.NonceAt(
s.ctx, s.cfg.Driver.WalletAddr(), nil, s.ctx, s.cfg.Driver.WalletAddr(), nil,
) )
...@@ -116,6 +141,8 @@ func (s *Service) eventLoop() { ...@@ -116,6 +141,8 @@ func (s *Service) eventLoop() {
} }
nonce := new(big.Int).SetUint64(nonce64) nonce := new(big.Int).SetUint64(nonce64)
// Construct the transaction submission clousure that will attempt
// to send the next transaction at the given nonce and gas price.
sendTx := func( sendTx := func(
ctx context.Context, ctx context.Context,
gasPrice *big.Int, gasPrice *big.Int,
...@@ -123,20 +150,38 @@ func (s *Service) eventLoop() { ...@@ -123,20 +150,38 @@ func (s *Service) eventLoop() {
log.Info(name+" attempting batch tx", "start", start, log.Info(name+" attempting batch tx", "start", start,
"end", end, "nonce", nonce, "end", end, "nonce", nonce,
"gasPrice", gasPrice) "gasPrice", gasPrice)
return s.cfg.Driver.SubmitBatchTx(
tx, err := s.cfg.Driver.SubmitBatchTx(
ctx, start, end, nonce, gasPrice, ctx, start, end, nonce, gasPrice,
) )
if err != nil {
return nil, err
}
s.metrics.BatchSizeInBytes.Observe(float64(tx.Size()))
return tx, nil
} }
// Wait until one of our submitted transactions confirms. If no
// receipt is received it's likely our gas price was too low.
batchConfirmationStart := time.Now()
receipt, err := s.txMgr.Send(s.ctx, sendTx) receipt, err := s.txMgr.Send(s.ctx, sendTx)
if err != nil { if err != nil {
log.Error(name+" unable to publish batch tx", log.Error(name+" unable to publish batch tx",
"err", err) "err", err)
s.metrics.FailedSubmissions.Inc()
continue continue
} }
// The transaction was successfully submitted.
log.Info(name+" batch tx successfully published", log.Info(name+" batch tx successfully published",
"tx_hash", receipt.TxHash) "tx_hash", receipt.TxHash)
batchConfirmationTime := time.Since(batchConfirmationStart) /
time.Millisecond
s.metrics.BatchConfirmationTime.Set(float64(batchConfirmationTime))
s.metrics.BatchesSubmitted.Inc()
s.metrics.SubmissionGasUsed.Observe(float64(receipt.GasUsed))
case err := <-s.ctx.Done(): case err := <-s.ctx.Done():
log.Error(name+" service shutting down", "err", err) log.Error(name+" service shutting down", "err", err)
...@@ -144,3 +189,10 @@ func (s *Service) eventLoop() { ...@@ -144,3 +189,10 @@ func (s *Service) eventLoop() {
} }
} }
} }
func weiToGwei64(wei *big.Int) float64 {
gwei := new(big.Float).SetInt(wei)
gwei.Mul(gwei, weiToGwei)
gwei64, _ := gwei.Float64()
return gwei64
}
# Changelog # Changelog
## 0.5.5
### Patch Changes
- 2924845d: expose ErrNonceTooHigh from miner
## 0.5.4 ## 0.5.4
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/l2geth", "name": "@eth-optimism/l2geth",
"version": "0.5.4", "version": "0.5.5",
"private": true, "private": true,
"devDependencies": {} "devDependencies": {}
} }
...@@ -33,11 +33,6 @@ export interface ICrossChainProvider { ...@@ -33,11 +33,6 @@ export interface ICrossChainProvider {
*/ */
l1ChainId: number l1ChainId: number
/**
* Chain ID for the L2 network.
*/
l2ChainId: number
/** /**
* Contract objects attached to their respective providers and addresses. * Contract objects attached to their respective providers and addresses.
*/ */
...@@ -129,9 +124,9 @@ export interface ICrossChainProvider { ...@@ -129,9 +124,9 @@ export interface ICrossChainProvider {
* *
* @param message Message to wait for. * @param message Message to wait for.
* @param opts Options to pass to the waiting function. * @param opts Options to pass to the waiting function.
* - `confirmations` (number): Number of transaction confirmations to wait for before returning. * @param opts.confirmations Number of transaction confirmations to wait for before returning.
* - `pollIntervalMs` (number): Number of milliseconds to wait between polling for the receipt. * @param opts.pollIntervalMs Number of milliseconds to wait between polling for the receipt.
* - `loopsBeforeTimeout` (number): Number of times to poll before timing out. * @param opts.timeoutMs Milliseconds to wait before timing out.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the * @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message. * given message.
*/ */
...@@ -140,7 +135,7 @@ export interface ICrossChainProvider { ...@@ -140,7 +135,7 @@ export interface ICrossChainProvider {
opts?: { opts?: {
confirmations?: number confirmations?: number
pollIntervalMs?: number pollIntervalMs?: number
loopsBeforeTimeout?: number timeoutMs?: number
} }
): Promise<MessageReceipt> ): Promise<MessageReceipt>
......
...@@ -6,37 +6,65 @@ import { ...@@ -6,37 +6,65 @@ import {
import { Signer } from '@ethersproject/abstract-signer' import { Signer } from '@ethersproject/abstract-signer'
import { Contract, BigNumber, Overrides } from 'ethers' import { Contract, BigNumber, Overrides } from 'ethers'
/**
* L1 contract references.
*/
export interface OEL1Contracts {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
/**
* L2 contract references.
*/
export interface OEL2Contracts {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
OVM_ETH: Contract
OVM_GasPriceOracle: Contract
OVM_SequencerFeeVault: Contract
WETH: Contract
}
/** /**
* Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate * Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate
* providers and addresses. * providers and addresses.
*/ */
export interface OEContracts { export interface OEContracts {
/** l1: OEL1Contracts
* L1 contract references. l2: OEL2Contracts
*/ }
l1: {
AddressManager: Contract
L1CrossDomainMessenger: Contract
L1StandardBridge: Contract
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
}
/** /**
* L2 contract references. * Convenience type for something that looks like the L1 OE contract interface but could be
*/ * addresses instead of actual contract objects.
l2: { */
L2CrossDomainMessenger: Contract export type OEL1ContractsLike = {
L2StandardBridge: Contract [K in keyof OEL1Contracts]: AddressLike
OVM_L1BlockNumber: Contract }
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract /**
OVM_ETH: Contract * Convenience type for something that looks like the L2 OE contract interface but could be
OVM_GasPriceOracle: Contract * addresses instead of actual contract objects.
OVM_SequencerFeeVault: Contract */
WETH: Contract export type OEL2ContractsLike = {
} [K in keyof OEL2Contracts]: AddressLike
}
/**
* Convenience type for something that looks like the OE contract interface but could be
* addresses instead of actual contract objects.
*/
export interface OEContractsLike {
l1: OEL1ContractsLike
l2: OEL2ContractsLike
} }
/** /**
...@@ -164,13 +192,6 @@ export interface StateRootBatch { ...@@ -164,13 +192,6 @@ export interface StateRootBatch {
stateRoots: string[] stateRoots: string[]
} }
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
/** /**
* Extended Ethers overrides object with an l2GasLimit field. * Extended Ethers overrides object with an l2GasLimit field.
* Only meant to be used for L1 to L2 messages, since L2 to L1 messages don't have a specified gas * Only meant to be used for L1 to L2 messages, since L2 to L1 messages don't have a specified gas
......
...@@ -4,46 +4,13 @@ import { ...@@ -4,46 +4,13 @@ import {
TransactionReceipt, TransactionReceipt,
TransactionResponse, TransactionResponse,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
import { getContractInterface } from '@eth-optimism/contracts' import { ethers, BigNumber } from 'ethers'
import { ethers } from 'ethers'
import { import {
ProviderLike, ProviderLike,
TransactionLike, TransactionLike,
DirectionlessCrossChainMessage, NumberLike,
} from './interfaces' AddressLike,
} from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimistic Ethereum smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
/** /**
* Converts a ProviderLike into a provider. Assumes that if the ProviderLike is a string then * Converts a ProviderLike into a provider. Assumes that if the ProviderLike is a string then
...@@ -84,3 +51,29 @@ export const toTransactionHash = (transaction: TransactionLike): string => { ...@@ -84,3 +51,29 @@ export const toTransactionHash = (transaction: TransactionLike): string => {
throw new Error('Invalid transaction') throw new Error('Invalid transaction')
} }
} }
/**
* Converts a number-like into an ethers BigNumber.
*
* @param num Number-like to convert into a BigNumber.
* @returns Number-like as a BigNumber.
*/
export const toBigNumber = (num: NumberLike): BigNumber => {
return ethers.BigNumber.from(num)
}
/**
* Converts an address-like into a 0x-prefixed address string.
*
* @param addr Address-like to convert into an address.
* @returns Address-like as an address.
*/
export const toAddress = (addr: AddressLike): string => {
if (typeof addr === 'string') {
assert(ethers.utils.isAddress(addr), 'Invalid address')
return ethers.utils.getAddress(addr)
} else {
assert(ethers.utils.isAddress(addr.address), 'Invalid address')
return ethers.utils.getAddress(addr.address)
}
}
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { ethers, Contract } from 'ethers'
import {
OEContracts,
OEL1Contracts,
OEL2Contracts,
OEContractsLike,
OEL2ContractsLike,
AddressLike,
} from '../interfaces'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
/**
* Full list of default L2 contract addresses.
*/
export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger,
L2StandardBridge: predeploys.L2StandardBridge,
OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber,
OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
OVM_DeployerWhitelist: predeploys.OVM_DeployerWhitelist,
OVM_ETH: predeploys.OVM_ETH,
OVM_GasPriceOracle: predeploys.OVM_GasPriceOracle,
OVM_SequencerFeeVault: predeploys.OVM_SequencerFeeVault,
WETH: predeploys.WETH9,
}
/**
* We've changed some contract names in this SDK to be a bit nicer. Here we remap these nicer names
* back to the original contract names so we can look them up.
*/
const NAME_REMAPPING = {
AddressManager: 'Lib_AddressManager',
OVM_L1BlockNumber: 'iOVM_L1BlockNumber',
WETH: 'WETH9',
}
/**
* Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the
* given network. Simplifies the process of getting the correct contract addresses for a given
* contract name.
*/
export const CONTRACT_ADDRESSES: {
[l1ChainId: number]: OEContractsLike
} = {
// Mainnet
1: {
l1: {
AddressManager: '0xdE1FCfB0851916CA5101820A69b13a4E276bd81F',
L1CrossDomainMessenger: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1',
L1StandardBridge: '0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1',
StateCommitmentChain: '0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19',
CanonicalTransactionChain: '0x5E4e65926BA27467555EB562121fac00D24E9dD2',
BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Kovan
42: {
l1: {
AddressManager: '0x100Dd3b414Df5BbA2B542864fF94aF8024aFdf3a',
L1CrossDomainMessenger: '0x4361d0F75A0186C05f971c566dC6bEa5957483fD',
L1StandardBridge: '0x22F24361D548e5FaAfb36d1437839f080363982B',
StateCommitmentChain: '0xD7754711773489F31A0602635f3F167826ce53C5',
CanonicalTransactionChain: '0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74',
BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
// Goerli
5: {
l1: {
AddressManager: '0x2F7E3cAC91b5148d336BbffB224B4dC79F09f01D',
L1CrossDomainMessenger: '0xEcC89b9EDD804850C4F343A278Be902be11AaF42',
L1StandardBridge: '0x73298186A143a54c20ae98EEE5a025bD5979De02',
StateCommitmentChain: '0x1afcA918eff169eE20fF8AB6Be75f3E872eE1C1A',
CanonicalTransactionChain: '0x2ebA8c4EfDB39A8Cd8f9eD65c50ec079f7CEBD81',
BondManager: '0xE5AE60bD6F8DEe4D0c2BC9268e23B92F1cacC58F',
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
}
/**
* Returns an ethers.Contract object for the given name, connected to the appropriate address for
* the given L1 chain ID. Users can also provide a custom address to connect the contract to
* instead. If the chain ID is not known then the user MUST provide a custom address or this
* function will throw an error.
*
* @param contractName Name of the contract to connect to.
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contract.
* @param opts.address Custom address to connect to the contract.
* @param opts.signerOrProvider Signer or provider to connect to the contract.
* @returns An ethers.Contract object connected to the appropriate address and interface.
*/
export const getOEContract = (
contractName: keyof OEL1Contracts | keyof OEL2Contracts,
l1ChainId: number,
opts: {
address?: AddressLike
signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {}
): Contract => {
const addresses = CONTRACT_ADDRESSES[l1ChainId]
if (addresses === undefined && opts.address === undefined) {
throw new Error(
`cannot get contract ${contractName} for unknown L1 chain ID ${l1ChainId}, you must provide an address`
)
}
return new Contract(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
getContractInterface(NAME_REMAPPING[contractName] || contractName),
opts.signerOrProvider
)
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L1 chain ID. The
* user can provide custom contract address overrides for L1 or L2 contracts. If the given chain ID
* is not known then the user MUST provide custom contract addresses for ALL L1 contracts or this
* function will throw an error.
*
* @param l1ChainId Chain ID for the L1 network where the OE contracts are deployed.
* @param opts Additional options for connecting to the contracts.
* @param opts.l1SignerOrProvider: Signer or provider to connect to the L1 contracts.
* @param opts.l2SignerOrProvider: Signer or provider to connect to the L2 contracts.
* @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
* @returns An object containing ethers.Contract objects connected to the appropriate addresses on
* both L1 and L2.
*/
export const getAllOEContracts = (
l1ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l1ChainId] || {
l1: {
AddressManager: undefined,
L1CrossDomainMessenger: undefined,
L1StandardBridge: undefined,
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts: OEL1Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
})
}
// Attach all L2 contracts.
const l2Contracts: OEL2Contracts = {} as any
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(contractName as any, l1ChainId, {
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
})
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
export * from './coercion'
export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { DirectionlessCrossChainMessage } from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimistic Ethereum smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: DirectionlessCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
/**
* Utility type for deep partials.
*/
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>
}
...@@ -3,16 +3,52 @@ import './setup' ...@@ -3,16 +3,52 @@ import './setup'
describe('CrossChainProvider', () => { describe('CrossChainProvider', () => {
describe('construction', () => { describe('construction', () => {
describe('basic construction (given L1 and L2 providers)', () => { describe('when given an ethers provider for the L1 provider', () => {
it('should have an l1Provider', () => {}) it('should use the provider as the L1 provider', () => {})
})
describe('when given an ethers provider for the L2 provider', () => {
it('should use the provider as the L2 provider', () => {})
})
describe('when given a string as the L1 provider', () => {
it('should create a JSON-RPC provider for the L1 provider', () => {})
})
describe('when given a string as the L2 provider', () => {
it('should create a JSON-RPC provider for the L2 provider', () => {})
})
describe('when no custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use the contract addresses for the known chain ID', () => {})
})
it('should have an l2Provider', () => {}) describe('when given an unknown chain ID', () => {
it('should throw an error', () => {})
})
})
it('should have an l1ChainId', () => {}) describe('when custom contract addresses are provided', () => {
describe('when given a known chain ID', () => {
it('should use known addresses except where custom addresses are given', () => {})
})
it('should have an l2ChainId', () => {}) describe('when given an unknown chain ID', () => {
describe('when all L1 addresses are provided', () => {
describe('when not all L2 addresses are provided', () => {
it('should use the custom L1 addresses and the default L2 addresses', () => {})
})
it('should have all contract connections', () => {}) describe('when all L2 addresses are provided', () => {
it('should use the custom addresses everywhere', () => {})
})
})
describe('when not all L1 addresses are provided', () => {
it('should throw an error', () => {})
})
})
}) })
}) })
...@@ -152,7 +188,7 @@ describe('CrossChainProvider', () => { ...@@ -152,7 +188,7 @@ describe('CrossChainProvider', () => {
}) })
describe('when the message has not been relayed', () => { describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY') it('should return a status of READY_FOR_RELAY', () => {})
}) })
}) })
}) })
......
import { expect } from './setup' import { expect } from '../setup'
import { Provider } from '@ethersproject/abstract-provider' import { Provider } from '@ethersproject/abstract-provider'
import { Contract, Signer } from 'ethers' import { Contract } from 'ethers'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts' import { toProvider, toTransactionHash } from '../../src'
import {
toProvider,
toTransactionHash,
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../src'
describe('utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('type coercion utils', () => {
describe('toProvider', () => { describe('toProvider', () => {
it('should convert a string to a JsonRpcProvider', () => { it('should convert a string to a JsonRpcProvider', () => {
const provider = toProvider('http://localhost:8545') const provider = toProvider('http://localhost:8545')
......
This diff is collapsed.
import { expect } from '../setup'
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import {
CrossChainMessage,
MessageDirection,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../../src'
describe('message encoding utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CrossChainMessage = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment