Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nice-steaks-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/data-transport-layer': patch
---

Handle unprotected transactions
6 changes: 6 additions & 0 deletions .changeset/old-moons-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
---

Allow for unprotected transactions
2 changes: 1 addition & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
if: failure()
uses: jwalton/gh-docker-logs@v1
with:
images: 'ethereumoptimism/hardhat,ops_deployer,ops_dtl,ethereumoptimism/l2geth,ethereumoptimism/message-relayer,ops_batch_submitter,ethereumoptimism/l2geth,ops_integration_tests'
images: 'ethereumoptimism/hardhat,ops_deployer,ops_dtl,ops_l2geth,ethereumoptimism/message-relayer,ops_batch_submitter,ops_replica,ops_integration_tests'
dest: '/home/runner/logs'

- name: Tar logs
Expand Down
1 change: 1 addition & 0 deletions integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@eth-optimism/contracts": "0.5.4",
"@eth-optimism/core-utils": "0.7.2",
"@eth-optimism/message-relayer": "0.2.4",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
Expand Down
75 changes: 75 additions & 0 deletions integration-tests/test/replica.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
gasPriceForL2,
sleep,
isLiveNetwork,
} from './shared/utils'
import { expect } from 'chai'
import { TransactionReceipt } from '@ethersproject/abstract-provider'

describe('Replica Tests', () => {
let env: OptimismEnv

before(async () => {
env = await OptimismEnv.new()
})

describe('Matching blocks', () => {
if (isLiveNetwork()) {
console.log('Skipping replica tests on live network')
return
}

it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
const result = await env.l2Wallet.sendTransaction(tx)

let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}

const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any

const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any

expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})

it('sync an unprotected tx (eip155)', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.l2Provider.sendTransaction(signed)

let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}

const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any

const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any

expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
})
})
11 changes: 7 additions & 4 deletions integration-tests/test/rpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,19 @@ describe('Basic RPC tests', () => {
).to.be.rejectedWith('invalid transaction: invalid sender')
})

it('should not accept a transaction without a chain ID', async () => {
it('should accept a transaction without a chain ID', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await wallet.signTransaction(tx)
const response = await provider.sendTransaction(signed)

await expect(
provider.sendTransaction(await wallet.signTransaction(tx))
).to.be.rejectedWith('Cannot submit unprotected transaction')
expect(response.chainId).to.equal(0)
const v = response.v
expect(v === 27 || v === 28).to.be.true
})

it('should accept a transaction with a value', async () => {
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/test/shared/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getAddressManager,
l1Provider,
l2Provider,
replicaProvider,
l1Wallet,
l2Wallet,
fundUser,
Expand Down Expand Up @@ -52,6 +53,7 @@ export class OptimismEnv {
// The providers
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider

constructor(args: any) {
this.addressManager = args.addressManager
Expand All @@ -67,6 +69,7 @@ export class OptimismEnv {
this.l2Wallet = args.l2Wallet
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.ctc = args.ctc
this.scc = args.scc
}
Expand Down Expand Up @@ -126,6 +129,7 @@ export class OptimismEnv {
l2Wallet,
l1Provider,
l2Provider,
replicaProvider,
})
}

Expand Down
12 changes: 9 additions & 3 deletions integration-tests/test/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,19 @@ const env = cleanEnv(process.env, {
export const l1Provider = new providers.JsonRpcProvider(env.L1_URL)
l1Provider.pollingInterval = env.L1_POLLING_INTERVAL

export const l2Provider = new providers.JsonRpcProvider(env.L2_URL)
export const l2Provider = injectL2Context(
new providers.JsonRpcProvider(env.L2_URL)
)
l2Provider.pollingInterval = env.L2_POLLING_INTERVAL

export const verifierProvider = new providers.JsonRpcProvider(env.VERIFIER_URL)
export const verifierProvider = injectL2Context(
new providers.JsonRpcProvider(env.VERIFIER_URL)
)
verifierProvider.pollingInterval = env.VERIFIER_POLLING_INTERVAL

export const replicaProvider = new providers.JsonRpcProvider(env.REPLICA_URL)
export const replicaProvider = injectL2Context(
new providers.JsonRpcProvider(env.REPLICA_URL)
)
replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL

// The sequencer private key which is funded on L1
Expand Down
3 changes: 0 additions & 3 deletions l2geth/internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1573,9 +1573,6 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {

// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if !tx.Protected() {
return common.Hash{}, errors.New("Cannot submit unprotected transaction")
}
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
Expand Down
33 changes: 20 additions & 13 deletions l2geth/rollup/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ type RollupClient interface {

// Client is an HTTP based RollupClient
type Client struct {
client *resty.Client
signer *types.EIP155Signer
client *resty.Client
chainID *big.Int
}

// TransactionResponse represents the response from the remote server when
Expand Down Expand Up @@ -166,11 +166,10 @@ func NewClient(url string, chainID *big.Int) *Client {
}
return nil
})
signer := types.NewEIP155Signer(chainID)

return &Client{
client: client,
signer: &signer,
client: client,
chainID: chainID,
}
}

Expand Down Expand Up @@ -322,7 +321,7 @@ func (c *Client) GetLatestTransactionBatchIndex() (*uint64, error) {

// batchedTransactionToTransaction converts a transaction into a
// types.Transaction that can be consumed by the SyncService
func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signer) (*types.Transaction, error) {
func batchedTransactionToTransaction(res *transaction, chainID *big.Int) (*types.Transaction, error) {
// `nil` transactions are not found
if res == nil {
return nil, errElementNotFound
Expand Down Expand Up @@ -373,7 +372,15 @@ func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signe
sig := make([]byte, crypto.SignatureLength)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
sig[64] = byte(res.Decoded.Signature.V)

var signer types.Signer
if res.Decoded.Signature.V == 27 || res.Decoded.Signature.V == 28 {
signer = types.HomesteadSigner{}
sig[64] = byte(res.Decoded.Signature.V - 27)
} else {
signer = types.NewEIP155Signer(chainID)
sig[64] = byte(res.Decoded.Signature.V)
}

tx, err := tx.WithSignature(signer, sig[:])
if err != nil {
Expand Down Expand Up @@ -431,7 +438,7 @@ func (c *Client) GetTransaction(index uint64, backend Backend) (*types.Transacti
if !ok {
return nil, fmt.Errorf("could not get tx with index %d", index)
}
return batchedTransactionToTransaction(res.Transaction, c.signer)
return batchedTransactionToTransaction(res.Transaction, c.chainID)
}

// GetLatestTransaction will get the latest transaction, meaning the transaction
Expand All @@ -452,7 +459,7 @@ func (c *Client) GetLatestTransaction(backend Backend) (*types.Transaction, erro
return nil, errors.New("Cannot get latest transaction")
}

return batchedTransactionToTransaction(res.Transaction, c.signer)
return batchedTransactionToTransaction(res.Transaction, c.chainID)
}

// GetEthContext will return the EthContext by block number
Expand Down Expand Up @@ -564,7 +571,7 @@ func (c *Client) GetLatestTransactionBatch() (*Batch, []*types.Transaction, erro
if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response")
}
return parseTransactionBatchResponse(txBatch, c.signer)
return parseTransactionBatchResponse(txBatch, c.chainID)
}

// GetTransactionBatch will return the transaction batch by batch index
Expand All @@ -584,19 +591,19 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response")
}
return parseTransactionBatchResponse(txBatch, c.signer)
return parseTransactionBatchResponse(txBatch, c.chainID)
}

// parseTransactionBatchResponse will turn a TransactionBatchResponse into a
// Batch and its corresponding types.Transactions
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, signer *types.EIP155Signer) (*Batch, []*types.Transaction, error) {
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, chainID *big.Int) (*Batch, []*types.Transaction, error) {
if txBatch == nil || txBatch.Batch == nil {
return nil, nil, errElementNotFound
}
batch := txBatch.Batch
txs := make([]*types.Transaction, len(txBatch.Transactions))
for i, tx := range txBatch.Transactions {
transaction, err := batchedTransactionToTransaction(tx, signer)
transaction, err := batchedTransactionToTransaction(tx, chainID)
if err != nil {
return nil, nil, fmt.Errorf("Cannot parse transaction batch: %w", err)
}
Expand Down
8 changes: 5 additions & 3 deletions ops/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ services:
depends_on:
- dtl
deploy:
replicas: 0
replicas: 1
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
Expand All @@ -181,8 +181,8 @@ services:
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
ports:
- ${L2GETH_HTTP_PORT:-8549}:8545
- ${L2GETH_WS_PORT:-8550}:8546
- ${REPLICA_HTTP_PORT:-8549}:8545
- ${REPLICA_WS_PORT:-8550}:8546

integration_tests:
deploy:
Expand All @@ -195,6 +195,8 @@ services:
environment:
L1_URL: http://l1_chain:8545
L2_URL: http://l2geth:8545
REPLICA_URL: http://replica:8545
VERIFIER_URL: http://verifier:8545
URL: http://deployer:8081/addresses.json
ENABLE_GAS_REPORT: 1
NO_NETWORK: 1
Expand Down
10 changes: 8 additions & 2 deletions packages/data-transport-layer/src/utils/eth-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
import { ethers } from 'ethers'

export const parseSignatureVParam = (
v: number | ethers.BigNumber,
v: number | ethers.BigNumber | string,
chainId: number
): number => {
return ethers.BigNumber.from(v).toNumber() - 2 * chainId - 35
v = ethers.BigNumber.from(v).toNumber()
// Handle unprotected transactions
if (v === 27 || v === 28) {
return v
}
// Handle EIP155 transactions
return v - 2 * chainId - 35
}