Skip to content

Commit

Permalink
L1Timestamp: set initial value correctly (ethereum#158)
Browse files Browse the repository at this point in the history
* L1Timestamp: set initial value correctly

* rollup: tx len check

* rollup: better tx len check

* rollup: tests

* rollup: lint
  • Loading branch information
tynes authored Dec 14, 2020
1 parent bfc80bf commit f1fdce0
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 26 deletions.
7 changes: 5 additions & 2 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
bc.currentBlock.Store(nilBlock)
bc.currentFastBlock.Store(nilBlock)

// TODO: Make default current timestamp configurable & make 0 if genesis else load from last block?

// Initialize the chain with ancient data if it isn't empty.
if bc.empty() {
rawdb.InitDatabaseFromFreezer(bc.db)
Expand Down Expand Up @@ -523,6 +521,11 @@ func (bc *BlockChain) CurrentBlock() *types.Block {
return bc.currentBlock.Load().(*types.Block)
}

// SetCurrentBlock is used for testing
func (bc *BlockChain) SetCurrentBlock(block *types.Block) {
bc.currentBlock.Store(block)
}

// CurrentFastBlock retrieves the current fast-sync head block of the canonical
// chain. The block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentFastBlock() *types.Block {
Expand Down
34 changes: 15 additions & 19 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
Expand Down Expand Up @@ -785,21 +784,6 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
break
}

// OVM Change - set the timestamp on the header to the
// timestamp of the transaction. Since there is an assumption
// of only 1 transaction, only do this for the first tx.
if vm.UsingOVM {
if len(w.current.txs) == 0 {
if tx.L1Timestamp() == 0 {
ts := w.eth.SyncService().GetLatestL1Timestamp()
bn := w.eth.SyncService().GetLatestL1BlockNumber()
tx.SetL1Timestamp(ts)
tx.SetL1BlockNumber(bn)
}
w.current.header.Time = tx.L1Timestamp()
}
}

// Error may be ignored here. The error has already been checked
// during transaction acceptance is the transaction pool.
//
Expand Down Expand Up @@ -880,16 +864,28 @@ func (w *worker) commitNewTx(tx *types.Transaction) error {
tstart := time.Now()

parent := w.chain.CurrentBlock()
// TODO: the timestamp is 0 until a l1 to l2 tx happens
timestamp := w.chain.CurrentTimestamp()
// The L1Timestamp will always be set for a transaction
// coming from a batch submission because the transaction
// has been included in the canonical transaction chain.
// The only time that L1Timestamp is zero is for queue
// origin sequencer transactions that have yet to be included
// in the canonical transaction chain, meaning this code
// path is only relevant for the sequencer.
if tx.L1Timestamp() == 0 {
ts := w.eth.SyncService().GetLatestL1Timestamp()
bn := w.eth.SyncService().GetLatestL1BlockNumber()
tx.SetL1Timestamp(ts)
tx.SetL1BlockNumber(bn)
}
timestamp := tx.L1Timestamp()

num := parent.Number()
header := &types.Header{
ParentHash: parent.Hash(),
Number: num.Add(num, common.Big1),
GasLimit: core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil),
Extra: w.extra,
Time: uint64(timestamp),
Time: timestamp,
}
if err := w.engine.Prepare(w.chain, header); err != nil {
return fmt.Errorf("Failed to prepare header for mining: %w", err)
Expand Down
47 changes: 42 additions & 5 deletions rollup/sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type EthereumClient interface {
NetworkID(context.Context) (*big.Int, error)
SyncProgress(context.Context) (*ethereum.SyncProgress, error)
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
BlockByNumber(context.Context, *big.Int) (*types.Block, error)
TransactionByHash(context.Context, common.Hash) (*types.Transaction, bool, error)
}

Expand Down Expand Up @@ -278,11 +279,6 @@ func (s *SyncService) Start() error {
}
s.Eth1Data = eth1Data

// TODO: LatestL1ToL2 fields will be 0 until a L1 to L2
// transaction takes place. This means that queue origin
// sequencer txs will have a L1BlockNumber of 0 until there
// is a L1 to L2 tx

_, client, err := s.dialEth1Node()
if err != nil {
return fmt.Errorf("Cannot dial eth1 nodes: %w", err)
Expand Down Expand Up @@ -311,6 +307,13 @@ func (s *SyncService) Start() error {
return fmt.Errorf("Bad sync status: %w", err)
}

// Set the initial values of the `LatestL1BlockNumber`
// and `LatestL1Timestamp`
err = s.initializeLatestL1()
if err != nil {
return fmt.Errorf("Cannot set latest L1: %w", err)
}

go s.LogDoneProcessing()
// Catch up to the tip of the eth1 chain
err = s.processHistoricalLogs()
Expand Down Expand Up @@ -338,6 +341,40 @@ func (s *SyncService) Start() error {
return nil
}

// initializeLatestL1 sets the initial values of the `L1BlockNumber`
// and `L1Timestamp` to the deploy height of the Canonical Transaction
// chain if the chain is empty, otherwise set it from the last
// transaction processed.
func (s *SyncService) initializeLatestL1() error {
block := s.bc.CurrentBlock()
if block == nil {
return errors.New("Current block is nil")
}
if block == s.bc.Genesis() {
if s.ctcDeployHeight == nil {
return errors.New("Must configure with canonical transaction chain deploy height")
}
var err error
block, err = s.ethclient.BlockByNumber(s.ctx, s.ctcDeployHeight)
if err != nil {
return fmt.Errorf("Cannot fetch ctc deploy block at height %d", s.ctcDeployHeight)
}
s.SetLatestL1Timestamp(block.Time())
s.SetLatestL1BlockNumber(block.Number().Uint64())
} else {
txs := block.Transactions()
if len(txs) != 1 {
log.Error("Unexpected number of transactions in block: %d", len(txs))
}
if len(txs) > 0 {
tx := txs[0]
s.SetLatestL1Timestamp(tx.L1Timestamp())
s.SetLatestL1BlockNumber(tx.L1BlockNumber().Uint64())
}
}
return nil
}

func (s *SyncService) getCommonAncestor(index *big.Int, list *[]*types.Header) (uint64, error) {
header, err := s.ethclient.HeaderByNumber(s.ctx, index)
if err != nil {
Expand Down
87 changes: 87 additions & 0 deletions rollup/sync_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rollup
import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"testing"
Expand Down Expand Up @@ -372,6 +373,75 @@ func TestSyncServiceSequencerBatchAppend(t *testing.T) {
}
}

func TestInitializeL1ContextGenesis(t *testing.T) {
service, _, _, err := newTestSyncService()
if err != nil {
t.Fatal(err)
}

// number and time are used to assert
// equality on the LatestL1BlockNumber
// and the LatestL1Timestamp
number := big.NewInt(1)
timestamp := uint64(1)
header := types.Header{
Number: number,
Time: timestamp,
}
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})

mockEthClient(service, map[string]interface{}{
"BlockByNumber": []*types.Block{block},
})
err = service.initializeLatestL1()
if err != nil {
t.Fatal(err)
}

latestL1Timestamp := service.GetLatestL1Timestamp()
latestL1BlockNumber := service.GetLatestL1BlockNumber()
if number.Uint64() != latestL1BlockNumber {
t.Fatal("Block numbers do not match")
}
if latestL1Timestamp != timestamp {
t.Fatal("Timestamps do not match")
}
}

func TestInitializeL1ContextPostGenesis(t *testing.T) {
service, _, _, err := newTestSyncService()
if err != nil {
t.Fatal(err)
}

header := types.Header{
Number: big.NewInt(0),
Time: 0,
}
tx := types.Transaction{}

number := uint64(1)
timestamp := uint64(1)
tx.SetL1Timestamp(timestamp)
tx.SetL1BlockNumber(number)
block := types.NewBlock(&header, []*types.Transaction{&tx}, []*types.Header{}, []*types.Receipt{})
service.bc.SetCurrentBlock(block)

err = service.initializeLatestL1()
if err != nil {
t.Fatal(err)
}

latestL1Timestamp := service.GetLatestL1Timestamp()
latestL1BlockNumber := service.GetLatestL1BlockNumber()
if number != latestL1BlockNumber {
t.Fatalf("number does not match, got %d, expected %d", latestL1BlockNumber, number)
}
if latestL1Timestamp != timestamp {
t.Fatal("timestamp does not match")
}
}

func newTestSyncService() (*SyncService, chan core.NewTxsEvent, event.Subscription, error) {
chainCfg := params.AllEthashProtocolChanges
chainID := big.NewInt(420)
Expand Down Expand Up @@ -419,6 +489,8 @@ func mockEthClient(service *SyncService, responses map[string]interface{}) {
type mockEthereumClient struct {
transactionByHashCallCount int
transactionByHashResponses []*types.Transaction
blockByNumberCallCount int
blockByNumberResponses []*types.Block
}

func (m *mockEthereumClient) ChainID(context.Context) (*big.Int, error) {
Expand All @@ -438,21 +510,36 @@ func (m *mockEthereumClient) HeaderByNumber(context.Context, *big.Int) (*types.H
func (m *mockEthereumClient) TransactionByHash(context.Context, common.Hash) (*types.Transaction, bool, error) {
if m.transactionByHashCallCount < len(m.transactionByHashResponses) {
res := m.transactionByHashResponses[m.transactionByHashCallCount]
m.transactionByHashCallCount += 1
return res, false, nil
}
t := types.Transaction{}
return &t, false, nil
}
func (m *mockEthereumClient) BlockByNumber(context.Context, *big.Int) (*types.Block, error) {
if m.blockByNumberCallCount < len(m.blockByNumberResponses) {
res := m.blockByNumberResponses[m.blockByNumberCallCount]
m.blockByNumberCallCount += 1
return res, nil
}
return nil, errors.New("Unexpected number of calls to BlockByNumber")
}

func newMockEthereumClient(responses map[string]interface{}) *mockEthereumClient {
transactionByHashResponses := []*types.Transaction{}
blockByNumberResponses := []*types.Block{}

txByHash, ok := responses["TransactionByHash"]
if ok {
transactionByHashResponses = txByHash.([]*types.Transaction)
}
blockByNumber, ok := responses["BlockByNumber"]
if ok {
blockByNumberResponses = blockByNumber.([]*types.Block)
}
return &mockEthereumClient{
transactionByHashResponses: transactionByHashResponses,
blockByNumberResponses: blockByNumberResponses,
}
}

Expand Down

0 comments on commit f1fdce0

Please sign in to comment.