diff --git a/integration-tests/test/replica.spec.ts b/integration-tests/test/replica.spec.ts index 165461d90fe8c..6f157ae2feb18 100644 --- a/integration-tests/test/replica.spec.ts +++ b/integration-tests/test/replica.spec.ts @@ -71,5 +71,32 @@ describe('Replica Tests', () => { expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot) expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash) }) + + it('should forward tx to sequencer', async () => { + const tx = { + ...defaultTransactionFactory(), + nonce: await env.l2Wallet.getTransactionCount(), + gasPrice: await gasPriceForL2(env), + } + const signed = await env.l2Wallet.signTransaction(tx) + const result = await env.replicaProvider.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) + }) }) }) diff --git a/l2geth/cmd/geth/main.go b/l2geth/cmd/geth/main.go index 6ca66ab7021d5..c38c993670b06 100644 --- a/l2geth/cmd/geth/main.go +++ b/l2geth/cmd/geth/main.go @@ -163,6 +163,7 @@ var ( utils.RollupEnforceFeesFlag, utils.RollupFeeThresholdDownFlag, utils.RollupFeeThresholdUpFlag, + utils.SequencerClientHttpFlag, } rpcFlags = []cli.Flag{ diff --git a/l2geth/cmd/geth/usage.go b/l2geth/cmd/geth/usage.go index 87d8ac6c4b74b..9a9321addbd43 100644 --- a/l2geth/cmd/geth/usage.go +++ b/l2geth/cmd/geth/usage.go @@ -77,6 +77,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.RollupEnforceFeesFlag, utils.RollupFeeThresholdDownFlag, utils.RollupFeeThresholdUpFlag, + utils.SequencerClientHttpFlag, }, }, { diff --git a/l2geth/cmd/utils/flags.go b/l2geth/cmd/utils/flags.go index 3195889dbac68..96cd012a36122 100644 --- a/l2geth/cmd/utils/flags.go +++ b/l2geth/cmd/utils/flags.go @@ -861,6 +861,11 @@ var ( Usage: "Allow txs with fees above the current fee up to this amount, must be > 1", EnvVar: "ROLLUP_FEE_THRESHOLD_UP", } + SequencerClientHttpFlag = cli.StringFlag{ + Name: "sequencer.clienthttp", + Usage: "HTTP endpoint for the sequencer client", + EnvVar: "SEQUENCER_CLIENT_HTTP", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1137,6 +1142,9 @@ func setRollup(ctx *cli.Context, cfg *rollup.Config) { val := ctx.GlobalFloat64(RollupFeeThresholdUpFlag.Name) cfg.FeeThresholdUp = new(big.Float).SetFloat64(val) } + if ctx.GlobalIsSet(SequencerClientHttpFlag.Name) { + cfg.SequencerClientHttp = ctx.GlobalString(SequencerClientHttpFlag.Name) + } } // setLes configures the les server and ultra light client settings from the command line flags. diff --git a/l2geth/eth/api_backend.go b/l2geth/eth/api_backend.go index 0f4ee88c1e72b..342163c48f59c 100644 --- a/l2geth/eth/api_backend.go +++ b/l2geth/eth/api_backend.go @@ -136,6 +136,10 @@ func (b *EthAPIBackend) IngestTransactions(txs []*types.Transaction) error { return nil } +func (b *EthAPIBackend) SequencerClientHttp() string { + return b.eth.config.Rollup.SequencerClientHttp +} + func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { // Pending block is only known by the miner if number == rpc.PendingBlockNumber { diff --git a/l2geth/internal/ethapi/api.go b/l2geth/internal/ethapi/api.go index bbb33ea1a6b47..8240b3b1a762d 100644 --- a/l2geth/internal/ethapi/api.go +++ b/l2geth/internal/ethapi/api.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum-optimism/optimism/l2geth/core/types" "github.com/ethereum-optimism/optimism/l2geth/core/vm" "github.com/ethereum-optimism/optimism/l2geth/crypto" + "github.com/ethereum-optimism/optimism/l2geth/ethclient" "github.com/ethereum-optimism/optimism/l2geth/log" "github.com/ethereum-optimism/optimism/l2geth/p2p" "github.com/ethereum-optimism/optimism/l2geth/params" @@ -51,6 +52,12 @@ import ( var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method") +const ( + // defaultDialTimeout is default duration the service will wait on + // startup to make a connection to either the L1 or L2 backends. + defaultDialTimeout = 5 * time.Second +) + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -1289,6 +1296,18 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa return nil } +// dialSequencerClientWithTimeout attempts to dial the Sequencer using the +// provided URL. If the dial doesn't complete within defaultDialTimeout +// seconds, this method will return an error. +func dialSequencerClientWithTimeout(ctx context.Context, url string) ( + *ethclient.Client, error) { + + ctxt, cancel := context.WithTimeout(ctx, defaultDialTimeout) + defer cancel() + + return ethclient.DialContext(ctxt, url) +} + // PublicTransactionPoolAPI exposes methods for the RPC interface type PublicTransactionPoolAPI struct { b Backend @@ -1649,18 +1668,27 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen // SendRawTransaction will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce. func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) { - if s.b.IsVerifier() { - return common.Hash{}, errors.New("Cannot send raw transaction in verifier mode") + tx := new(types.Transaction) + if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + return common.Hash{}, err } if s.b.IsSyncing() { return common.Hash{}, errors.New("Cannot send raw transaction while syncing") } - tx := new(types.Transaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { - return common.Hash{}, err + if s.b.IsVerifier() { + client, err := dialSequencerClientWithTimeout(ctx, s.b.SequencerClientHttp()) + if err != nil { + return common.Hash{}, err + } + err = client.SendTransaction(context.Background(), tx) + if err != nil { + return common.Hash{}, err + } + return tx.Hash(), nil } + // L1Timestamp and L1BlockNumber will be set right before execution txMeta := types.NewTransactionMeta(nil, 0, nil, types.QueueOriginSequencer, nil, nil, encodedTx) tx.SetTransactionMeta(txMeta) diff --git a/l2geth/internal/ethapi/backend.go b/l2geth/internal/ethapi/backend.go index df8aef030ace4..a6a21f43339a8 100644 --- a/l2geth/internal/ethapi/backend.go +++ b/l2geth/internal/ethapi/backend.go @@ -96,6 +96,7 @@ type Backend interface { SuggestL2GasPrice(context.Context) (*big.Int, error) SetL2GasPrice(context.Context, *big.Int) error IngestTransactions([]*types.Transaction) error + SequencerClientHttp() string } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/l2geth/les/api_backend.go b/l2geth/les/api_backend.go index c95fd87ab98f8..24623f6eb5ccc 100644 --- a/l2geth/les/api_backend.go +++ b/l2geth/les/api_backend.go @@ -86,6 +86,10 @@ func (b *LesApiBackend) IngestTransactions([]*types.Transaction) error { panic("not implemented") } +func (b *LesApiBackend) SequencerClientHttp() string { + return b.eth.config.Rollup.SequencerClientHttp +} + func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { return b.eth.blockchain.CurrentHeader(), nil diff --git a/l2geth/rollup/config.go b/l2geth/rollup/config.go index 6d28466549d3e..c13e1394c7921 100644 --- a/l2geth/rollup/config.go +++ b/l2geth/rollup/config.go @@ -37,4 +37,6 @@ type Config struct { // quoted and the transaction being executed FeeThresholdDown *big.Float FeeThresholdUp *big.Float + // HTTP endpoint of the sequencer + SequencerClientHttp string } diff --git a/ops/docker-compose.yml b/ops/docker-compose.yml index 1c428bbebfda9..5995605949ebc 100644 --- a/ops/docker-compose.yml +++ b/ops/docker-compose.yml @@ -155,6 +155,7 @@ services: replica: depends_on: - dtl + - l2geth deploy: replicas: 1 build: @@ -165,6 +166,7 @@ services: - ./envs/geth.env environment: ETH1_HTTP: http://l1_chain:8545 + SEQUENCER_CLIENT_HTTP: http://l2geth:8545 ROLLUP_STATE_DUMP_PATH: http://deployer:8081/state-dump.latest.json ROLLUP_CLIENT_HTTP: http://dtl:7878 ROLLUP_BACKEND: 'l2'