Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #260 from ethereum-optimism/jg/l1_source_cleanup
Browse files Browse the repository at this point in the history
ref impl: Flatten sources & Cleanup Sync Start
  • Loading branch information
protolambda committed Mar 22, 2022
2 parents 98660ff + 22ccfed commit dd07121
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 270 deletions.
88 changes: 74 additions & 14 deletions opnode/l1/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package l1

import (
"context"
"errors"
"fmt"
"math/big"

Expand All @@ -13,6 +14,8 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
)

const MaxBlocksInL1Range = uint64(100)

type Source struct {
client *ethclient.Client
downloader *Downloader
Expand All @@ -25,20 +28,6 @@ func NewSource(client *ethclient.Client) Source {
}
}

func (s Source) BlockLinkByNumber(ctx context.Context, num uint64) (self eth.BlockID, parent eth.BlockID, err error) {
header, err := s.client.HeaderByNumber(ctx, big.NewInt(int64(num)))
if err != nil {
// w%: wrap the error, we still need to detect if a canonical block is not found, a.k.a. end of chain.
return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("failed to determine block-hash of height %d, could not get header: %w", num, err)
}
parentNum := num
if parentNum > 0 {
parentNum -= 1
}
return eth.BlockID{Hash: header.Hash(), Number: num}, eth.BlockID{Hash: header.ParentHash, Number: parentNum}, nil

}

func (s Source) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
return s.client.SubscribeNewHead(ctx, ch)
}
Expand Down Expand Up @@ -74,6 +63,7 @@ func (s Source) Close() {
func (s Source) FetchL1Info(ctx context.Context, id eth.BlockID) (derive.L1Info, error) {
return s.client.BlockByHash(ctx, id.Hash)
}

func (s Source) FetchReceipts(ctx context.Context, id eth.BlockID) ([]*types.Receipt, error) {
_, receipts, err := s.Fetch(ctx, id)
return receipts, err
Expand All @@ -91,3 +81,73 @@ func (s Source) FetchTransactions(ctx context.Context, window []eth.BlockID) ([]
return txns, nil

}
func (s Source) L1HeadBlockRef(ctx context.Context) (eth.L1BlockRef, error) {
return s.l1BlockRefByNumber(ctx, nil)
}

func (s Source) L1BlockRefByNumber(ctx context.Context, l1Num uint64) (eth.L1BlockRef, error) {
return s.l1BlockRefByNumber(ctx, new(big.Int).SetUint64(l1Num))
}

// l1BlockRefByNumber wraps l1.HeaderByNumber to return an eth.L1BlockRef
// This is internal because the exposed L1BlockRefByNumber takes uint64 instead of big.Ints
func (s Source) l1BlockRefByNumber(ctx context.Context, number *big.Int) (eth.L1BlockRef, error) {
header, err := s.client.HeaderByNumber(ctx, number)
if err != nil {
// w%: wrap the error, we still need to detect if a canonical block is not found, a.k.a. end of chain.
return eth.L1BlockRef{}, fmt.Errorf("failed to determine block-hash of height %v, could not get header: %w", number, err)
}
l1Num := header.Number.Uint64()
parentNum := l1Num
if parentNum > 0 {
parentNum -= 1
}
return eth.L1BlockRef{
Self: eth.BlockID{Hash: header.Hash(), Number: l1Num},
Parent: eth.BlockID{Hash: header.ParentHash, Number: parentNum},
}, nil
}

// L1Range returns a range of L1 block beginning just after `begin`.
func (s Source) L1Range(ctx context.Context, begin eth.BlockID) ([]eth.BlockID, error) {
// Ensure that we start on the expected chain.
if canonicalBegin, err := s.L1BlockRefByNumber(ctx, begin.Number); err != nil {
return nil, fmt.Errorf("failed to fetch L1 block %v %v: %w", begin.Number, begin.Hash, err)
} else {
if canonicalBegin.Self != begin {
return nil, fmt.Errorf("Re-org at begin block. Expected: %v. Actual: %v", begin, canonicalBegin.Self)
}
}

l1head, err := s.L1HeadBlockRef(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch head L1 block: %w", err)
}
maxBlocks := MaxBlocksInL1Range
// Cap maxBlocks if there are less than maxBlocks between `begin` and the head of the chain.
if l1head.Self.Number-begin.Number <= maxBlocks {
maxBlocks = l1head.Self.Number - begin.Number
}

if maxBlocks == 0 {
return nil, nil
}

prevHash := begin.Hash
var res []eth.BlockID
// TODO: Walk backwards to be able to use block by hash
for i := begin.Number + 1; i < begin.Number+maxBlocks+1; i++ {
n, err := s.L1BlockRefByNumber(ctx, i)
if err != nil {
return nil, fmt.Errorf("failed to fetch L1 block %v: %w", i, err)
}
// TODO(Joshua): Look into why this fails around the genesis block
if n.Parent.Number != 0 && n.Parent.Hash != prevHash {
return nil, errors.New("re-organization occurred while attempting to get l1 range")
}
prevHash = n.Self.Hash
res = append(res, n.Self)
}

return res, nil
}
39 changes: 32 additions & 7 deletions opnode/l2/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"math/big"
"time"

"github.com/ethereum-optimism/optimistic-specs/opnode/eth"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
Expand All @@ -14,16 +17,18 @@ import (
)

type Source struct {
rpc *rpc.Client // raw RPC client. Used for the consensus namespace
client *ethclient.Client // go-ethereum's wrapper around the rpc client for the eth namespace
log log.Logger
rpc *rpc.Client // raw RPC client. Used for the consensus namespace
client *ethclient.Client // go-ethereum's wrapper around the rpc client for the eth namespace
genesis *rollup.Genesis
log log.Logger
}

func NewSource(l2Node *rpc.Client, log log.Logger) (*Source, error) {
func NewSource(ll2Node *rpc.Client, genesis *rollup.Genesis, log log.Logger) (*Source, error) {
return &Source{
rpc: l2Node,
client: ethclient.NewClient(l2Node),
log: log,
rpc: ll2Node,
client: ethclient.NewClient(ll2Node),
genesis: genesis,
log: log,
}, nil
}

Expand Down Expand Up @@ -123,3 +128,23 @@ func (s *Source) GetPayload(ctx context.Context, payloadId PayloadID) (*Executio
e.Debug("Received payload")
return &result, nil
}

// L2BlockRefByNumber returns the canonical block and parent ids.
func (s *Source) L2BlockRefByNumber(ctx context.Context, l2Num *big.Int) (eth.L2BlockRef, error) {
block, err := s.client.BlockByNumber(ctx, l2Num)
if err != nil {
// w%: wrap the error, we still need to detect if a canonical block is not found, a.k.a. end of chain.
return eth.L2BlockRef{}, fmt.Errorf("failed to determine block-hash of height %v, could not get header: %w", l2Num, err)
}
return derive.BlockReferences(block, s.genesis)
}

// L2BlockRefByHash returns the block & parent ids based on the supplied hash. The returned BlockRef may not be in the canonical chain
func (s *Source) L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error) {
block, err := s.client.BlockByHash(ctx, l2Hash)
if err != nil {
// w%: wrap the error, we still need to detect if a canonical block is not found, a.k.a. end of chain.
return eth.L2BlockRef{}, fmt.Errorf("failed to determine block-hash of height %v, could not get header: %w", l2Hash, err)
}
return derive.BlockReferences(block, s.genesis)
}
3 changes: 2 additions & 1 deletion opnode/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func New(ctx context.Context, cfg *Config, log log.Logger) (*OpNode, error) {
// l1Node.SetHeader()
l1Source := l1.NewSource(ethclient.NewClient(l1Node))
var l2Engines []*driver.Driver
genesis := cfg.Rollup.Genesis

for i, addr := range cfg.L2EngineAddrs {
l2Node, err := dialRPCClientWithBackoff(ctx, log, addr)
Expand All @@ -69,7 +70,7 @@ func New(ctx context.Context, cfg *Config, log log.Logger) (*OpNode, error) {
}
// TODO: we may need to authenticate the connection with L2
// backend.SetHeader()
client, err := l2.NewSource(l2Node, log.New("engine_client", i))
client, err := l2.NewSource(l2Node, &genesis, log.New("engine_client", i))
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions opnode/rollup/derive/payload_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"

"github.com/ethereum-optimism/optimistic-specs/opnode/l2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie"
Expand Down Expand Up @@ -270,7 +270,7 @@ func SortedAndPreparedBatches(batches []*BatchData, epoch, blockTime, minL2Time,
return out
}

func L1InfoDepositBytes(l1Info L1Info) (l2.Data, error) {
func L1InfoDepositBytes(l1Info L1Info) (hexutil.Bytes, error) {
l1Tx := types.NewTx(L1InfoDeposit(l1Info))
opaqueL1Tx, err := l1Tx.MarshalBinary()
if err != nil {
Expand All @@ -279,12 +279,12 @@ func L1InfoDepositBytes(l1Info L1Info) (l2.Data, error) {
return opaqueL1Tx, nil
}

func DeriveDeposits(epoch uint64, receipts []*types.Receipt) ([]l2.Data, error) {
func DeriveDeposits(epoch uint64, receipts []*types.Receipt) ([]hexutil.Bytes, error) {
userDeposits, err := UserDeposits(epoch, receipts)
if err != nil {
return nil, fmt.Errorf("failed to derive user deposits: %v", err)
}
encodedTxs := make([]l2.Data, 0, len(userDeposits))
encodedTxs := make([]hexutil.Bytes, 0, len(userDeposits))
for i, tx := range userDeposits {
opaqueTx, err := types.NewTx(tx).MarshalBinary()
if err != nil {
Expand Down
64 changes: 36 additions & 28 deletions opnode/rollup/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package driver

import (
"context"
"math/big"

"github.com/ethereum-optimism/optimistic-specs/opnode/eth"
"github.com/ethereum-optimism/optimistic-specs/opnode/l1"
"github.com/ethereum-optimism/optimistic-specs/opnode/l2"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup/derive"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup/sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)

Expand All @@ -21,23 +22,52 @@ type BatchSubmitter interface {
Submit(config *rollup.Config, batches []*derive.BatchData) (common.Hash, error)
}

type Downloader interface {
// FetchL1Info fetches the L1 header information corresponding to a L1 block ID
FetchL1Info(ctx context.Context, id eth.BlockID) (derive.L1Info, error)
// FetchReceipts of a L1 block
FetchReceipts(ctx context.Context, id eth.BlockID) ([]*types.Receipt, error)
// FetchTransactions from the given window of L1 blocks
FetchTransactions(ctx context.Context, window []eth.BlockID) ([]*types.Transaction, error)
}

type Engine interface {
GetPayload(ctx context.Context, payloadId l2.PayloadID) (*l2.ExecutionPayload, error)
ForkchoiceUpdate(ctx context.Context, state *l2.ForkchoiceState, attr *l2.PayloadAttributes) (*l2.ForkchoiceUpdatedResult, error)
ExecutePayload(ctx context.Context, payload *l2.ExecutionPayload) error
BlockByHash(context.Context, common.Hash) (*types.Block, error)
}

type L1Chain interface {
L1BlockRefByNumber(ctx context.Context, l1Num uint64) (eth.L1BlockRef, error)
L1HeadBlockRef(ctx context.Context) (eth.L1BlockRef, error)
L1Range(ctx context.Context, base eth.BlockID) ([]eth.BlockID, error)
}

// TODO: Extend L2 Interface to get safe/unsafe blocks (specifically for Unsafe L2 head)
type L2Chain interface {
L2BlockRefByNumber(ctx context.Context, l2Num *big.Int) (eth.L2BlockRef, error)
L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error)
}

type outputInterface interface {
step(ctx context.Context, l2Head eth.BlockID, l2Finalized eth.BlockID, unsafeL2Head eth.BlockID, l1Input []eth.BlockID) (eth.BlockID, error)
newBlock(ctx context.Context, l2Finalized eth.BlockID, l2Parent eth.BlockID, l2Safe eth.BlockID, l1Origin eth.BlockID, includeDeposits bool) (eth.BlockID, *derive.BatchData, error)
}

func NewDriver(cfg rollup.Config, l2 *l2.Source, l1 *l1.Source, log log.Logger, submitter BatchSubmitter, sequencer bool) *Driver {
if sequencer && submitter == nil {
log.Error("Bad configuration")
// TODO: return error
}
input := &inputImpl{
chainSource: sync.NewChainSource(l1, l2, &cfg.Genesis),
genesis: &cfg.Genesis,
}
output := &outputImpl{
Config: cfg,
dl: l1,
l2: l2,
log: log,
}
return &Driver{
s: NewState(log, cfg, input, output, submitter, sequencer),
s: NewState(log, cfg, l1, l2, output, submitter, sequencer),
}
}

Expand All @@ -47,25 +77,3 @@ func (d *Driver) Start(ctx context.Context, l1Heads <-chan eth.L1BlockRef) error
func (d *Driver) Close() error {
return d.s.Close()
}

type inputImpl struct {
chainSource sync.ChainSource
genesis *rollup.Genesis
}

func (i *inputImpl) L1Head(ctx context.Context) (eth.L1BlockRef, error) {
return i.chainSource.L1HeadBlockRef(ctx)
}

func (i *inputImpl) L2Head(ctx context.Context) (eth.L2BlockRef, error) {
return i.chainSource.L2BlockRefByNumber(ctx, nil)

}

func (i *inputImpl) L1ChainWindow(ctx context.Context, base eth.BlockID) ([]eth.BlockID, error) {
return sync.FindL1Range(ctx, i.chainSource, base)
}

func (i *inputImpl) SafeL2Head(ctx context.Context) (eth.L2BlockRef, error) {
return sync.FindSafeL2Head(ctx, i.chainSource, i.genesis)
}
25 changes: 23 additions & 2 deletions opnode/rollup/driver/fake_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/ethereum-optimism/optimistic-specs/opnode/eth"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"
"github.com/ethereum-optimism/optimistic-specs/opnode/rollup/sync"
)

func fakeGenesis(l1 rune, l2 rune, l2offset int) rollup.Genesis {
Expand Down Expand Up @@ -89,6 +88,27 @@ type fakeChainSource struct {
log log.Logger
}

func (m *fakeChainSource) L1Range(ctx context.Context, base eth.BlockID) ([]eth.BlockID, error) {
var out []eth.BlockID
found := false
for i, b := range m.l1s[m.l1reorg] {
if found {
out = append(out, b.Self)
}
if b.Self == base {
found = true
}
if i == m.l1head {
if found {
return out, nil
} else {
return nil, ethereum.NotFound
}
}
}
return nil, ethereum.NotFound
}

func (m *fakeChainSource) L1BlockRefByNumber(ctx context.Context, l1Num uint64) (eth.L1BlockRef, error) {
m.log.Trace("L1BlockRefByNumber", "l1Num", l1Num, "l1Head", m.l1head, "reorg", m.l1reorg)
if l1Num > uint64(m.l1head) {
Expand Down Expand Up @@ -131,7 +151,8 @@ func (m *fakeChainSource) L2BlockRefByHash(ctx context.Context, l2Hash common.Ha
return eth.L2BlockRef{}, ethereum.NotFound
}

var _ sync.ChainSource = (*fakeChainSource)(nil)
var _ L1Chain = (*fakeChainSource)(nil)
var _ L2Chain = (*fakeChainSource)(nil)

func (m *fakeChainSource) reorgL1() {
m.log.Trace("Reorg L1", "new_reorg", m.l1reorg+1, "old_reorg", m.l1reorg)
Expand Down
Loading

0 comments on commit dd07121

Please sign in to comment.