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

l2geth: remove the miner from the hot code path #875

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0285248
l2geth: add Backend enums and config parsing
tynes Apr 21, 2021
c15ddb9
l2geth: move OVMContext to types file
tynes Apr 21, 2021
5df96f7
l2geth: implement syncservice spec
tynes Apr 22, 2021
895cb81
l2geth: fix error handling for get tx batch
tynes Apr 22, 2021
de5975f
l2geth: update tests to compile and pass
tynes Apr 22, 2021
ebdb8e2
l2geth: add sync range functions
tynes Apr 22, 2021
54acadb
l2geth: add batch index indexing
tynes Apr 22, 2021
fc32a32
l2geth: update syncservice hot path logging
tynes Apr 22, 2021
3ce74df
l2geth: use indexed batch index
tynes Apr 22, 2021
2480b2d
chore: add changeset
tynes Apr 22, 2021
7b49d6e
l2geth: WIP remove miner
tynes Apr 20, 2021
656cbe3
l2geth: add Backend enums and config parsing
tynes Apr 21, 2021
97fd394
l2geth: move OVMContext to types file
tynes Apr 21, 2021
60f3a2e
l2geth: implement syncservice spec
tynes Apr 22, 2021
d8359ce
l2geth: fix error handling for get tx batch
tynes Apr 22, 2021
87742d9
l2geth: update tests to compile and pass
tynes Apr 22, 2021
82aea42
l2geth: add sync range functions
tynes Apr 22, 2021
c0c3561
l2geth: add batch index indexing
tynes Apr 22, 2021
6aba901
l2geth: update syncservice hot path logging
tynes Apr 22, 2021
c902766
l2geth: use indexed batch index
tynes Apr 22, 2021
570b79a
chore: add changeset
tynes Apr 22, 2021
0256d91
progress: debugging
tynes May 4, 2021
964e05e
l2geth: sync spec refactor (#822)
tynes May 12, 2021
6d9e632
Merge branch 'develop' into l2geth/sync-spec
tynes May 12, 2021
c3bedee
sync-service: better logline
tynes May 12, 2021
e8a350d
l2geth: better logline
tynes May 12, 2021
db1f547
l2geth: test apply indexed transaction
tynes May 12, 2021
0849703
l2geth: better logline
tynes May 12, 2021
059d793
linting: fix
tynes May 12, 2021
913a14d
syncservice: fix logline
tynes May 12, 2021
51ef245
l2geth: add error and fix logline
tynes May 13, 2021
8b5ead5
l2geth: sync service tests
tynes May 13, 2021
cac3daf
fix: get last confirmed enqueue (#846)
tynes May 12, 2021
53b12c5
batch-submitter: updated config (#847)
tynes May 13, 2021
9d91c2c
l2geth: update rawdb logline
tynes May 13, 2021
1b83b4b
l2geth: more robust testing
tynes May 13, 2021
9ada2d0
l2geth: add sanity check for L1ToL2 timestamps
tynes May 13, 2021
4c9ceb7
l2geth: handle error in single place
tynes May 13, 2021
1f86620
Merge branch 'develop' into l2geth/sync-spec
tynes May 13, 2021
435e25d
l2geth: fix test tx queue origin
tynes May 14, 2021
f7d18c1
l2geth: add new arg to start.sh
tynes May 14, 2021
c38ec87
l2geth: return error in the SyncService.Start()
tynes May 14, 2021
5f78294
Merge branch 'l2geth/sync-spec' into l2geth/sync-spec-remove-miner
tynes May 14, 2021
5d41ede
dtl: fix replica tx indexing (#872)
tynes May 14, 2021
c05f1cd
l2geth: WIP more progress
tynes May 14, 2021
b7222f5
bugfix: prepare statedb so logs are captured
tynes May 15, 2021
3510a67
style: less code on single line
tynes May 15, 2021
1d2d550
integration-tests: use gasprice 0 by default
tynes May 26, 2021
73f6082
l2geth: TEMP debug logs
tynes May 26, 2021
f8225ce
l2geth: TEMP log contract calls
tynes May 26, 2021
2a845f4
l2geth: debug nonce problems
tynes May 26, 2021
f4fbf12
l2geth: debug sync service
tynes May 26, 2021
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/kind-cows-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/data-transport-layer': patch
---

Fixes a bug where L2 synced transactions were not RLP encoded
5 changes: 5 additions & 0 deletions .changeset/nervous-bobcats-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eth-optimism/l2geth": patch
---

Refactor the SyncService to more closely implement the specification. This includes using query params to select the backend from the DTL, trailing syncing of batches for the sequencer, syncing by batches as the verifier as well as unified code paths for transaction ingestion to prevent double ingestion or missed ingestion
1 change: 1 addition & 0 deletions integration-tests/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const config: HardhatUserConfig = {
optimism: {
url: process.env.L2_URL || 'http://localhost:8545',
ovm: true,
gasPrice: 0,
},
},
solidity: '0.7.6',
Expand Down
2 changes: 1 addition & 1 deletion l2geth/cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ var (
utils.Eth1ETHGatewayAddressFlag,
utils.Eth1ChainIdFlag,
utils.RollupClientHttpFlag,
// Enable verifier mode
utils.RollupEnableVerifierFlag,
utils.RollupAddressManagerOwnerAddressFlag,
utils.RollupTimstampRefreshFlag,
Expand All @@ -166,6 +165,7 @@ var (
utils.RollupMaxCalldataSizeFlag,
utils.RollupDataPriceFlag,
utils.RollupExecutionPriceFlag,
utils.RollupBackendFlag,
}

rpcFlags = []cli.Flag{
Expand Down
1 change: 1 addition & 0 deletions l2geth/cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.RollupMaxCalldataSizeFlag,
utils.RollupDataPriceFlag,
utils.RollupExecutionPriceFlag,
utils.RollupBackendFlag,
},
},
{
Expand Down
15 changes: 15 additions & 0 deletions l2geth/cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,12 @@ var (
Value: time.Minute * 3,
EnvVar: "ROLLUP_TIMESTAMP_REFRESH",
}
RollupBackendFlag = cli.StringFlag{
Name: "rollup.backend",
Usage: "Sync backend for verifiers (\"l1\" or \"l2\"), defaults to l1",
Value: "l1",
EnvVar: "ROLLUP_BACKEND",
}
// Flag to enable verifier mode
RollupEnableVerifierFlag = cli.BoolFlag{
Name: "rollup.verifier",
Expand Down Expand Up @@ -1170,6 +1176,15 @@ func setRollup(ctx *cli.Context, cfg *rollup.Config) {
if ctx.GlobalIsSet(RollupExecutionPriceFlag.Name) {
cfg.ExecutionPrice = GlobalBig(ctx, RollupExecutionPriceFlag.Name)
}
if ctx.GlobalIsSet(RollupBackendFlag.Name) {
val := ctx.GlobalString(RollupBackendFlag.Name)
backend, err := rollup.NewBackend(val)
if err != nil {
log.Error("Configured with unknown sync backend, defaulting to l1", "backend", val)
backend, _ = rollup.NewBackend("l1")
}
cfg.Backend = backend
}
}

// setLes configures the les server and ultra light client settings from the command line flags.
Expand Down
5 changes: 5 additions & 0 deletions l2geth/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,10 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
// Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
reorg := externTd.Cmp(localTd) > 0
if vm.UsingOVM {
// Difficulty has no concept in the OVM
reorg = true
}
currentBlock = bc.CurrentBlock()
if !reorg && externTd.Cmp(localTd) == 0 {
// Split same-difficulty blocks by number, then preferentially select
Expand Down Expand Up @@ -1483,6 +1487,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
bc.futureBlocks.Remove(block.Hash())

if status == CanonStatTy {
log.Info("Sending chainFeed event")
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if len(logs) > 0 {
bc.logsFeed.Send(logs)
Expand Down
21 changes: 21 additions & 0 deletions l2geth/core/rawdb/rollup_indexes.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ func WriteHeadVerifiedIndex(db ethdb.KeyValueWriter, index uint64) {
log.Crit("Failed to store verifier index", "err", err)
}
}

// ReadHeadBatchIndex will read the known tip of the processed batches
func ReadHeadBatchIndex(db ethdb.KeyValueReader) *uint64 {
data, _ := db.Get(headBatchKey)
if len(data) == 0 {
return nil
}
ret := new(big.Int).SetBytes(data).Uint64()
return &ret
}

// WriteHeadBatchIndex will write the known tip of the processed batches
func WriteHeadBatchIndex(db ethdb.KeyValueWriter, index uint64) {
value := new(big.Int).SetUint64(index).Bytes()
if index == 0 {
value = []byte{0}
}
if err := db.Put(headBatchKey, value); err != nil {
log.Crit("Failed to store head batch index", "err", err)
}
}
2 changes: 2 additions & 0 deletions l2geth/core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ var (
headQueueIndexKey = []byte("LastQueueIndex")
// headVerifiedIndexKey tracks the latest verified index
headVerifiedIndexKey = []byte("LastVerifiedIndex")
// headBatchKey tracks the latest processed batch
headBatchKey = []byte("LastBatch")

preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
Expand Down
2 changes: 2 additions & 0 deletions l2geth/core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)

Expand Down Expand Up @@ -104,6 +105,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env)
log.Info("ApplyTransaction", "tx-hash", tx.Hash().Hex(), "nonce", msg.Nonce())
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, err
Expand Down
23 changes: 21 additions & 2 deletions l2geth/core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,25 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
// OVM_ENABLED
// Only log for non `eth_call`s
if evm.Context.EthCallSender == nil {
log.Debug("Calling contract", "ID", evm.Id, "Address", contract.Address().Hex(), "Data", hexutil.Encode(input))
var isUnknown = true
for name, account := range evm.chainConfig.StateDump.Accounts {
if contract.Address() == account.Address {
isUnknown = false
abi := &(account.ABI)
method, err := abi.MethodById(input)
if err != nil {
log.Debug("Calling Known Contract", "ID", evm.Id, "Name", name, "Message", err, "Data", hexutil.Encode(input))
} else {
log.Debug("Calling Known Contract", "ID", evm.Id, "Name", name, "Method", method.RawName, "Data", hexutil.Encode(input))
if method.RawName == "ovmREVERT" {
log.Debug("Contract Threw Exception", "ID", evm.Id, "asciified", string(input))
}
}
}
}
if isUnknown {
log.Debug("Calling Unknown Contract", "ID", evm.Id, "Address", contract.Address().Hex(), "Data", hexutil.Encode(input))
}
}

// Uncomment to make Safety checker always returns true.
Expand Down Expand Up @@ -689,5 +707,6 @@ func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
// trying to create to, and create to it.
func (evm *EVM) OvmADDRESS() common.Address {
slot := common.Hash{31: 0x0f}
return common.BytesToAddress(evm.StateDB.GetState(evm.Context.OvmExecutionManager.Address, slot).Bytes())
value := evm.StateDB.GetState(evm.Context.OvmExecutionManager.Address, slot)
return common.BytesToAddress(value.Bytes())
}
13 changes: 8 additions & 5 deletions l2geth/eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,12 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
}

func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending()
return state, block.Header(), nil
if !vm.UsingOVM {
// Pending state is only known by the miner
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This probably should be hidden still

const (
PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)

if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending()
return state, block.Header(), nil
}
}
// Otherwise resolve the block number and return its state
header, err := b.HeaderByNumber(ctx, number)
Expand All @@ -216,6 +218,7 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B
if header == nil {
return nil, nil, errors.New("header not found")
}
log.Info("Getting state db", "root", header.Root.Hex(), "number", number)
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
return stateDb, header, err
}
Expand Down Expand Up @@ -320,7 +323,7 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
}
}
}
return b.eth.syncService.ApplyTransaction(signedTx)
return b.eth.syncService.ValidateAndApplySequencerTransaction(signedTx)
}
// OVM Disabled
return b.eth.txPool.AddLocal(signedTx)
Expand Down
3 changes: 3 additions & 0 deletions l2geth/eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ func makeExtraData(extra []byte) []byte {

// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
if vm.UsingOVM {
return rollup.NewRollupEngine()
}
// If proof-of-authority is requested, set it up
if chainConfig.Clique != nil {
return clique.New(chainConfig.Clique, db)
Expand Down
13 changes: 8 additions & 5 deletions l2geth/internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1464,19 +1464,22 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx cont
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
// Ask transaction pool for the nonce which includes pending transactions
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := s.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
if !vm.UsingOVM {
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := s.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
}
return (*hexutil.Uint64)(&nonce), nil
}
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
nonce := state.GetNonce(address)
log.Info("GetTransactionCount", "address", address.Hex(), "nonce", nonce)
return (*hexutil.Uint64)(&nonce), state.Error()
}

Expand Down
4 changes: 2 additions & 2 deletions l2geth/miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// channel directly to the miner
worker.rollupSub = eth.SyncService().SubscribeNewTxsEvent(worker.rollupCh)
//worker.rollupSub = eth.SyncService().SubscribeNewTxsEvent(worker.rollupCh)

// Subscribe events for blockchain
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
Expand Down Expand Up @@ -408,7 +408,7 @@ func (w *worker) mainLoop() {
defer w.txsSub.Unsubscribe()
defer w.chainHeadSub.Unsubscribe()
defer w.chainSideSub.Unsubscribe()
defer w.rollupSub.Unsubscribe()
//defer w.rollupSub.Unsubscribe()

for {
select {
Expand Down
65 changes: 57 additions & 8 deletions l2geth/rollup/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,17 @@ type decoded struct {
type RollupClient interface {
GetEnqueue(index uint64) (*types.Transaction, error)
GetLatestEnqueue() (*types.Transaction, error)
GetTransaction(uint64) (*types.Transaction, error)
GetLatestTransaction() (*types.Transaction, error)
GetLatestEnqueueIndex() (*uint64, error)
GetTransaction(uint64, Backend) (*types.Transaction, error)
GetLatestTransaction(Backend) (*types.Transaction, error)
GetLatestTransactionIndex(Backend) (*uint64, error)
GetEthContext(uint64) (*EthContext, error)
GetLatestEthContext() (*EthContext, error)
GetLastConfirmedEnqueue() (*types.Transaction, error)
GetLatestTransactionBatch() (*Batch, []*types.Transaction, error)
GetLatestTransactionBatchIndex() (*uint64, error)
GetTransactionBatch(uint64) (*Batch, []*types.Transaction, error)
SyncStatus() (*SyncStatus, error)
SyncStatus(Backend) (*SyncStatus, error)
GetL1GasPrice() (*big.Int, error)
}

Expand Down Expand Up @@ -272,6 +275,43 @@ func (c *Client) GetLatestEnqueue() (*types.Transaction, error) {
return tx, nil
}

// GetLatestEnqueueIndex returns the latest `enqueue()` index
func (c *Client) GetLatestEnqueueIndex() (*uint64, error) {
tx, err := c.GetLatestEnqueue()
if err != nil {
return nil, err
}
index := tx.GetMeta().QueueIndex
if index == nil {
return nil, errors.New("Latest queue index is nil")
}
return index, nil
}

// GetLatestTransactionIndex returns the latest CTC index that has been batch
// submitted or not, depending on the backend
func (c *Client) GetLatestTransactionIndex(backend Backend) (*uint64, error) {
tx, err := c.GetLatestTransaction(backend)
if err != nil {
return nil, err
}
index := tx.GetMeta().Index
if index == nil {
return nil, errors.New("Latest index is nil")
}
return index, nil
}

// GetLatestTransactionBatchIndex returns the latest transaction batch index
func (c *Client) GetLatestTransactionBatchIndex() (*uint64, error) {
batch, _, err := c.GetLatestTransactionBatch()
if err != nil {
return nil, err
}
index := batch.Index
return &index, nil
}

// 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) {
Expand Down Expand Up @@ -366,12 +406,15 @@ func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signe
}

// GetTransaction will get a transaction by Canonical Transaction Chain index
func (c *Client) GetTransaction(index uint64) (*types.Transaction, error) {
func (c *Client) GetTransaction(index uint64, backend Backend) (*types.Transaction, error) {
str := strconv.FormatUint(index, 10)
response, err := c.client.R().
SetPathParams(map[string]string{
"index": str,
}).
SetQueryParams(map[string]string{
"backend": backend.String(),
}).
SetResult(&TransactionResponse{}).
Get("/transaction/index/{index}")

Expand All @@ -387,9 +430,12 @@ func (c *Client) GetTransaction(index uint64) (*types.Transaction, error) {

// GetLatestTransaction will get the latest transaction, meaning the transaction
// with the greatest Canonical Transaction Chain index
func (c *Client) GetLatestTransaction() (*types.Transaction, error) {
func (c *Client) GetLatestTransaction(backend Backend) (*types.Transaction, error) {
response, err := c.client.R().
SetResult(&TransactionResponse{}).
SetQueryParams(map[string]string{
"backend": backend.String(),
}).
Get("/transaction/latest")

if err != nil {
Expand Down Expand Up @@ -479,9 +525,12 @@ func (c *Client) GetLastConfirmedEnqueue() (*types.Transaction, error) {
}

// SyncStatus will query the remote server to determine if it is still syncing
func (c *Client) SyncStatus() (*SyncStatus, error) {
func (c *Client) SyncStatus(backend Backend) (*SyncStatus, error) {
response, err := c.client.R().
SetResult(&SyncStatus{}).
SetQueryParams(map[string]string{
"backend": backend.String(),
}).
Get("/eth/syncing")

if err != nil {
Expand Down Expand Up @@ -535,8 +584,8 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
// parseTransactionBatchResponse will turn a TransactionBatchResponse into a
// Batch and its corresponding types.Transactions
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, signer *types.EIP155Signer) (*Batch, []*types.Transaction, error) {
if txBatch == nil {
return nil, nil, nil
if txBatch == nil || txBatch.Batch == nil {
return nil, nil, errElementNotFound
}
batch := txBatch.Batch
txs := make([]*types.Transaction, len(txBatch.Transactions))
Expand Down
2 changes: 2 additions & 0 deletions l2geth/rollup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ type Config struct {
DataPrice *big.Int
// The gas price to use for L2 congestion costs
ExecutionPrice *big.Int
// Represents the source of the transactions that is being synced
Backend Backend
}
Loading