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

add new RPC GetL2BlockRangeForL1 to fetch L2 block range for L1 block number #1860

Merged
merged 12 commits into from
Sep 25, 2023
2 changes: 1 addition & 1 deletion contracts
2 changes: 1 addition & 1 deletion go-ethereum
73 changes: 73 additions & 0 deletions nodeInterface/NodeInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,76 @@ 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.
func (n NodeInterface) BlockL1Num(c ctx, evm mech, l2BlockNum uint64) (uint64, error) {
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
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")
}

}
Loading