Commit a6f578c8 authored by Matthew Slipper's avatar Matthew Slipper

Merge remote-tracking branch 'upstream/develop' into feat/bss-multiple-txs

parents 36a43683 cebc747c
---
'@eth-optimism/proxyd': minor
---
Add X-Forwarded-For header when proxying RPCs on proxyd
---
'@eth-optimism/contracts': patch
---
Update hardhat task for managing the gas oracle
......@@ -7,16 +7,18 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
"io"
"io/ioutil"
"math"
"math/rand"
"net/http"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
)
const (
......@@ -84,6 +86,8 @@ type Backend struct {
maxRPS int
maxWSConns int
outOfServiceInterval time.Duration
stripTrailingXFF bool
proxydIP string
}
type BackendOpt func(b *Backend)
......@@ -140,6 +144,18 @@ func WithTLSConfig(tlsConfig *tls.Config) BackendOpt {
}
}
func WithStrippedTrailingXFF() BackendOpt {
return func(b *Backend) {
b.stripTrailingXFF = true
}
}
func WithProxydIP(ip string) BackendOpt {
return func(b *Backend) {
b.proxydIP = ip
}
}
func NewBackend(
name string,
rpcURL string,
......@@ -163,6 +179,10 @@ func NewBackend(
opt(backend)
}
if !backend.stripTrailingXFF && backend.proxydIP == "" {
log.Warn("proxied requests' XFF header will not contain the proxyd ip address")
}
return backend
}
......@@ -316,7 +336,18 @@ func (b *Backend) doForward(ctx context.Context, rpcReq *RPCReq) (*RPCRes, error
httpReq.SetBasicAuth(b.authUsername, b.authPassword)
}
xForwardedFor := GetXForwardedFor(ctx)
if b.stripTrailingXFF {
ipList := strings.Split(xForwardedFor, ", ")
if len(ipList) > 0 {
xForwardedFor = ipList[0]
}
} else if b.proxydIP != "" {
xForwardedFor = fmt.Sprintf("%s, %s", xForwardedFor, b.proxydIP)
}
httpReq.Header.Set("content-type", "application/json")
httpReq.Header.Set("X-Forwarded-For", xForwardedFor)
httpRes, err := b.client.Do(httpReq)
if err != nil {
......
......@@ -41,6 +41,7 @@ type BackendConfig struct {
CAFile string `toml:"ca_file"`
ClientCertFile string `toml:"client_cert_file"`
ClientKeyFile string `toml:"client_key_file"`
StripTrailingXFF bool `toml:"strip_trailing_xff"`
}
type BackendsConfig map[string]*BackendConfig
......
......@@ -96,6 +96,10 @@ func Start(config *Config) error {
log.Info("using custom TLS config for backend", "name", name)
opts = append(opts, WithTLSConfig(tlsConfig))
}
if cfg.StripTrailingXFF {
opts = append(opts, WithStrippedTrailingXFF())
}
opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP")))
back := NewBackend(name, rpcURL, wsURL, lim, opts...)
backendNames = append(backendNames, name)
backendsByName[name] = back
......
......@@ -5,20 +5,23 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors"
"io"
"net/http"
"strconv"
"time"
)
const (
ContextKeyAuth = "authorization"
ContextKeyReqID = "req_id"
ContextKeyXForwardedFor = "x_forwarded_for"
)
type Server struct {
......@@ -214,7 +217,16 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
return nil
}
xff := r.Header.Get("X-Forwarded-For")
if xff == "" {
ipPort := strings.Split(r.RemoteAddr, ":")
if len(ipPort) == 2 {
xff = ipPort[0]
}
}
ctx := context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization])
ctx = context.WithValue(ctx, ContextKeyXForwardedFor, xff)
return context.WithValue(
ctx,
ContextKeyReqID,
......@@ -271,3 +283,11 @@ func GetReqID(ctx context.Context) string {
}
return reqId
}
func GetXForwardedFor(ctx context.Context) string {
xff, ok := ctx.Value(ContextKeyXForwardedFor).(string)
if !ok {
return ""
}
return xff
}
......@@ -7,8 +7,25 @@ import { predeploys } from '../src/predeploys'
import { getContractDefinition } from '../src/contract-defs'
task('set-l2-gasprice')
.addOptionalParam('l2GasPrice', 'Gas Price to set on L2', 0, types.int)
.addOptionalParam(
'l2GasPrice',
'Gas Price to set on L2',
undefined,
types.int
)
.addOptionalParam('transactionGasPrice', 'tx.gasPrice', undefined, types.int)
.addOptionalParam(
'overhead',
'amortized additional gas used by each batch that users must pay for',
undefined,
types.int
)
.addOptionalParam(
'scalar',
'amount to scale up the gas to charge',
undefined,
types.int
)
.addOptionalParam(
'contractsRpcUrl',
'Sequencer HTTP Endpoint',
......@@ -42,10 +59,18 @@ task('set-l2-gasprice')
throw new Error(`Incorrect key. Owner ${owner}, Signer ${addr}`)
}
// List the current values
const gasPrice = await GasPriceOracle.callStatic.gasPrice()
console.log(`Gas Price is currently ${gasPrice.toString()}`)
console.log(`Setting Gas Price to ${args.l2GasPrice}`)
const scalar = await GasPriceOracle.callStatic.scalar()
const overhead = await GasPriceOracle.callStatic.overhead()
console.log('Current values:')
console.log(`Gas Price: ${gasPrice.toString()}`)
console.log(`Scalar: ${scalar.toString()}`)
console.log(`Overhead: ${overhead.toString()}`)
if (args.l2GasPrice !== undefined) {
console.log(`Setting gas price to ${args.l2GasPrice}`)
const tx = await GasPriceOracle.connect(signer).setGasPrice(
args.l2GasPrice,
{ gasPrice: args.transactionGasPrice }
......@@ -53,4 +78,26 @@ task('set-l2-gasprice')
const receipt = await tx.wait()
console.log(`Success - ${receipt.transactionHash}`)
}
if (args.scalar !== undefined) {
console.log(`Setting scalar to ${args.scalar}`)
const tx = await GasPriceOracle.connect(signer).setScalar(args.scalar, {
gasPrice: args.transactionGasPrice,
})
const receipt = await tx.wait()
console.log(`Success - ${receipt.transactionHash}`)
}
if (args.overhead !== undefined) {
console.log(`Setting overhead to ${args.overhead}`)
const tx = await GasPriceOracle.connect(signer).setOverhead(
args.overhead,
{ gasPrice: args.transactionGasPrice }
)
const receipt = await tx.wait()
console.log(`Success - ${receipt.transactionHash}`)
}
})
......@@ -5,6 +5,7 @@ import {
TransactionReceipt,
} from '@ethersproject/abstract-provider'
import { ethers, BigNumber, Event } from 'ethers'
import { sleep } from '@eth-optimism/core-utils'
import {
ICrossChainProvider,
OEContracts,
......@@ -395,15 +396,27 @@ export class CrossChainProvider implements ICrossChainProvider {
return null
}
public async waitForMessageReciept(
public async waitForMessageReceipt(
message: MessageLike,
opts?: {
opts: {
confirmations?: number
pollIntervalMs?: number
timeoutMs?: number
}
} = {}
): Promise<MessageReceipt> {
throw new Error('Not implemented')
let totalTimeMs = 0
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now()
const receipt = await this.getMessageReceipt(message)
if (receipt !== null) {
return receipt
} else {
await sleep(opts.pollIntervalMs || 4000)
totalTimeMs += Date.now() - tick
}
}
throw new Error(`timed out waiting for message receipt`)
}
public async estimateL2MessageGasLimit(
......
......@@ -186,7 +186,7 @@ export interface ICrossChainProvider {
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
waitForMessageReciept(
waitForMessageReceipt(
message: MessageLike,
opts?: {
confirmations?: number
......
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainERC20Pair', () => {
describe('construction', () => {
it('should have a messenger', () => {})
it('should have a messenger')
describe('when the token is a standard bridge token', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when the token is SNX', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when the token is DAI', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when a custom adapter is provided', () => {
it('should use the custom adapter', () => {})
it('should use the custom adapter')
})
})
describe('deposit', () => {
describe('when the user has enough balance and allowance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is ETH', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is SNX', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is DAI', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
it('should throw an error')
})
describe('when the user has not given enough allowance to the bridge', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('withdraw', () => {
describe('when the user has enough balance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is ETH', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is SNX', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is DAI', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('populateTransaction', () => {
describe('deposit', () => {
it('should populate the transaction with the correct values', () => {})
it('should populate the transaction with the correct values')
})
describe('withdraw', () => {
it('should populate the transaction with the correct values', () => {})
it('should populate the transaction with the correct values')
})
})
describe('estimateGas', () => {
describe('deposit', () => {
it('should estimate gas required for the transaction', () => {})
it('should estimate gas required for the transaction')
})
describe('withdraw', () => {
it('should estimate gas required for the transaction', () => {})
it('should estimate gas required for the transaction')
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainMessenger', () => {
describe('sendMessage', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit', () => {})
it('should send a message with an estimated l2GasLimit')
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit', () => {})
it('should send a message with the provided l2GasLimit')
})
})
describe('resendMessage', () => {
describe('when the message being resent exists', () => {
it('should resend the message with the new gas limit', () => {})
it('should resend the message with the new gas limit')
})
describe('when the message being resent does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('finalizeMessage', () => {
describe('when the message being finalized exists', () => {
describe('when the message is ready to be finalized', () => {
it('should finalize the message', () => {})
it('should finalize the message')
})
describe('when the message is not ready to be finalized', () => {
it('should throw an error', () => {})
it('should throw an error')
})
describe('when the message has already been finalized', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('when the message being finalized does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import { expect } from './setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract } from 'ethers'
......@@ -383,24 +382,26 @@ describe('CrossChainProvider', () => {
describe('getMessagesByAddress', () => {
describe('when the address has sent messages', () => {
describe('when no direction is specified', () => {
it('should find all messages sent by the address', () => {})
it('should find all messages sent by the address')
})
describe('when a direction is specified', () => {
it('should find all messages only in the given direction', () => {})
it('should find all messages only in the given direction')
})
describe('when a block range is specified', () => {
it('should find all messages within the block range', () => {})
it('should find all messages within the block range')
})
describe('when both a direction and a block range are specified', () => {
it('should find all messages only in the given direction and within the block range', () => {})
it(
'should find all messages only in the given direction and within the block range'
)
})
})
describe('when the address has not sent messages', () => {
it('should find nothing', () => {})
it('should find nothing')
})
})
......@@ -784,44 +785,44 @@ describe('CrossChainProvider', () => {
describe('getMessageStatus', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE', () => {})
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE')
})
describe('when the message has been executed on L2', () => {
it('should return a status of RELAYED', () => {})
it('should return a status of RELAYED')
})
describe('when the message has been executed but failed', () => {
it('should return a status of FAILED_L1_TO_L2_MESSAGE', () => {})
it('should return a status of FAILED_L1_TO_L2_MESSAGE')
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the message state root has not been published', () => {
it('should return a status of STATE_ROOT_NOT_PUBLISHED', () => {})
it('should return a status of STATE_ROOT_NOT_PUBLISHED')
})
describe('when the message state root is still in the challenge period', () => {
it('should return a status of IN_CHALLENGE_PERIOD', () => {})
it('should return a status of IN_CHALLENGE_PERIOD')
})
describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => {
it('should return a status of RELAYED', () => {})
it('should return a status of RELAYED')
})
describe('when the message has been relayed but the relay failed', () => {
it('should return a status of READY_FOR_RELAY', () => {})
it('should return a status of READY_FOR_RELAY')
})
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')
})
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
......@@ -982,64 +983,157 @@ describe('CrossChainProvider', () => {
// track of
})
describe('waitForMessageReciept', () => {
describe('waitForMessageReceipt', () => {
let l2Messenger: Contract
let provider: CrossChainProvider
beforeEach(async () => {
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
})
describe('when the message receipt already exists', () => {
it('should immediately return the receipt', () => {})
it('should immediately return the receipt', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
const tx = await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
const messageReceipt = await provider.waitForMessageReceipt(message)
expect(messageReceipt.receiptStatus).to.equal(1)
expect(
omit(messageReceipt.transactionReceipt, 'confirmations')
).to.deep.equal(
omit(
await ethers.provider.getTransactionReceipt(tx.hash),
'confirmations'
)
)
})
})
describe('when the message receipt does not exist already', () => {
describe('when no extra options are provided', () => {
it('should wait for the receipt to be published', () => {})
it('should wait forever for the receipt if the receipt is never published', () => {})
it('should wait for the receipt to be published', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
setTimeout(async () => {
await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
}, 5000)
const tick = Date.now()
const messageReceipt = await provider.waitForMessageReceipt(message)
const tock = Date.now()
expect(messageReceipt.receiptStatus).to.equal(1)
expect(tock - tick).to.be.greaterThan(5000)
})
describe('when a timeout is provided', () => {
it('should throw an error if the timeout is reached', () => {})
it('should wait forever for the receipt if the receipt is never published', () => {
// Not sure how to easily test this without introducing some sort of cancellation token
// I don't want the promise to loop forever and make the tests never finish.
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
describe('when a timeout is provided', () => {
it('should throw an error if the timeout is reached', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
await expect(
provider.waitForMessageReceipt(message, {
timeoutMs: 10000,
})
).to.be.rejectedWith('timed out waiting for message receipt')
})
})
})
})
describe('estimateL2MessageGasLimit', () => {
it('should perform a gas estimation of the L2 action', () => {})
it('should perform a gas estimation of the L2 action')
})
describe('estimateMessageWaitTimeBlocks', () => {
describe('when the message exists', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return the estimated blocks until the message will be confirmed on L2', () => {})
it(
'should return the estimated blocks until the message will be confirmed on L2'
)
})
describe('when the message has been executed on L2', () => {
it('should return 0', () => {})
it('should return 0')
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the state root has not been published', () => {
it('should return the estimated blocks until the state root will be published and pass the challenge period', () => {})
it(
'should return the estimated blocks until the state root will be published and pass the challenge period'
)
})
describe('when the state root is within the challenge period', () => {
it('should return the estimated blocks until the state root passes the challenge period', () => {})
it(
'should return the estimated blocks until the state root passes the challenge period'
)
})
describe('when the state root passes the challenge period', () => {
it('should return 0', () => {})
it('should return 0')
})
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('estimateMessageWaitTimeSeconds', () => {
it('should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time', () => {})
it(
'should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time'
)
})
})
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