Skip to content

Commit

Permalink
Merge pull request #1860 from OffchainLabs/add-l2rangeforl1-rpc
Browse files Browse the repository at this point in the history
add new RPC GetL2BlockRangeForL1 to fetch L2 block range for L1 block number
  • Loading branch information
ganeshvanahalli authored Sep 25, 2023
2 parents affaf25 + 39ce1b4 commit c86cf05
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 2 deletions.
2 changes: 1 addition & 1 deletion contracts
2 changes: 1 addition & 1 deletion go-ethereum
74 changes: 74 additions & 0 deletions nodeInterface/NodeInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,77 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h
calldataForL1 = data
return
}

// L2BlockRangeForL1 fetches the L1 block number of a given l2 block number.
// c ctx and evm mech arguments are not used but supplied to match the precompile function type in NodeInterface contract
func (n NodeInterface) BlockL1Num(c ctx, evm mech, l2BlockNum uint64) (uint64, error) {
blockHeader, err := n.backend.HeaderByNumber(n.context, rpc.BlockNumber(l2BlockNum))
if err != nil {
return 0, err
}
blockL1Num := types.DeserializeHeaderExtraInformation(blockHeader).L1BlockNumber
return blockL1Num, nil
}

func (n NodeInterface) matchL2BlockNumWithL1(c ctx, evm mech, l2BlockNum uint64, l1BlockNum uint64) error {
blockL1Num, err := n.BlockL1Num(c, evm, l2BlockNum)
if err != nil {
return fmt.Errorf("failed to get the L1 block number of the L2 block: %v. Error: %w", l2BlockNum, err)
}
if blockL1Num != l1BlockNum {
return fmt.Errorf("no L2 block was found with the given L1 block number. Found L2 block: %v with L1 block number: %v, given L1 block number: %v", l2BlockNum, blockL1Num, l1BlockNum)
}
return nil
}

// L2BlockRangeForL1 finds the first and last L2 block numbers that have the given L1 block number
func (n NodeInterface) L2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) (uint64, uint64, error) {
currentBlockNum := n.backend.CurrentBlock().Number.Uint64()
genesis := n.backend.ChainConfig().ArbitrumChainParams.GenesisBlockNum

storedMids := map[uint64]uint64{}
firstL2BlockForL1 := func(target uint64) (uint64, error) {
low, high := genesis, currentBlockNum
highBlockL1Num, err := n.BlockL1Num(c, evm, high)
if err != nil {
return 0, err
}
if highBlockL1Num < target {
return high + 1, nil
}
for low < high {
mid := arbmath.SaturatingUAdd(low, high) / 2
if _, ok := storedMids[mid]; !ok {
midBlockL1Num, err := n.BlockL1Num(c, evm, mid)
if err != nil {
return 0, err
}
storedMids[mid] = midBlockL1Num
}
if storedMids[mid] < target {
low = mid + 1
} else {
high = mid
}
}
return high, nil
}

firstBlock, err := firstL2BlockForL1(l1BlockNum)
if err != nil {
return 0, 0, fmt.Errorf("failed to get the first L2 block with the L1 block: %v. Error: %w", l1BlockNum, err)
}
lastBlock, err := firstL2BlockForL1(l1BlockNum + 1)
if err != nil {
return 0, 0, fmt.Errorf("failed to get the last L2 block with the L1 block: %v. Error: %w", l1BlockNum, err)
}

if err := n.matchL2BlockNumWithL1(c, evm, firstBlock, l1BlockNum); err != nil {
return 0, 0, err
}
lastBlock -= 1
if err = n.matchL2BlockNumWithL1(c, evm, lastBlock, l1BlockNum); err != nil {
return 0, 0, err
}
return firstBlock, lastBlock, nil
}
69 changes: 69 additions & 0 deletions system_tests/nodeinterface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

package arbtest

import (
"context"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/offchainlabs/nitro/arbos/util"
"github.com/offchainlabs/nitro/solgen/go/node_interfacegen"
)

func TestL2BlockRangeForL1(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

l2info, node, l2client, l1info, _, _, l1stack := createTestNodeOnL1(t, ctx, true)
defer requireClose(t, l1stack)
defer node.StopAndWait()
user := l1info.GetDefaultTransactOpts("User", ctx)

numTransactions := 200
for i := 0; i < numTransactions; i++ {
TransferBalanceTo(t, "Owner", util.RemapL1Address(user.From), big.NewInt(1e18), l2info, l2client, ctx)
}

nodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, l2client)
Require(t, err)

l1BlockNums := map[uint64]*[2]uint64{}
latestL2, err := l2client.BlockNumber(ctx)
Require(t, err)
for l2BlockNum := uint64(0); l2BlockNum <= latestL2; l2BlockNum++ {
l1BlockNum, err := nodeInterface.BlockL1Num(&bind.CallOpts{}, l2BlockNum)
Require(t, err)
if _, ok := l1BlockNums[l1BlockNum]; !ok {
l1BlockNums[l1BlockNum] = &[2]uint64{l2BlockNum, l2BlockNum}
} else {
l1BlockNums[l1BlockNum][1] = l2BlockNum
}
}

// Test success
for l1BlockNum := range l1BlockNums {
rng, err := nodeInterface.L2BlockRangeForL1(&bind.CallOpts{}, l1BlockNum)
Require(t, err)
expected := l1BlockNums[l1BlockNum]
if rng.FirstBlock != expected[0] || rng.LastBlock != expected[1] {
unexpectedL1BlockNum, err := nodeInterface.BlockL1Num(&bind.CallOpts{}, rng.LastBlock)
Require(t, err)
// Handle the edge case when new l2 blocks are produced between latestL2 was last calculated and now.
if unexpectedL1BlockNum != l1BlockNum || rng.LastBlock < expected[1] || rng.FirstBlock != expected[0] {
t.Errorf("L2BlockRangeForL1(%d) = (%d %d) want (%d %d)", l1BlockNum, rng.FirstBlock, rng.LastBlock, expected[0], expected[1])
}
}
}
// Test invalid case
finalValidL1BlockNumber, err := nodeInterface.BlockL1Num(&bind.CallOpts{}, latestL2)
Require(t, err)
if _, err := nodeInterface.L2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1); err == nil {
t.Fatalf("GetL2BlockRangeForL1 didn't fail for an invalid input")
}

}

0 comments on commit c86cf05

Please sign in to comment.