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
80 changes: 80 additions & 0 deletions nodeInterface/NodeInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,83 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h
calldataForL1 = data
return
}

func (n NodeInterface) getL1BlockNum(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
}
l1BlockNum := types.DeserializeHeaderExtraInformation(blockHeader).L1BlockNumber
return l1BlockNum, nil
}

func (n NodeInterface) GetL2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) ([]uint64, error) {
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
currentBlockNum := n.backend.CurrentBlock().Number.Uint64()
genesis := n.backend.ChainConfig().ArbitrumChainParams.GenesisBlockNum

checkCorrectness := func(blockNum uint64, target uint64) error {
blockL1Num, err := n.getL1BlockNum(blockNum)
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
}
if blockL1Num != target {
return errors.New("no L2 block was found with the given L1 block number")
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved

lowFirstBlock := genesis
highFirstBlock := currentBlockNum
lowLastBlock := genesis
highLastBlock := currentBlockNum
var storedMid uint64
var storedMidBlockL1Num uint64
for lowFirstBlock < highFirstBlock || lowLastBlock < highLastBlock {
if lowFirstBlock < highFirstBlock {
mid := arbmath.SaturatingUAdd(lowFirstBlock, highFirstBlock) / 2
midBlockL1Num, err := n.getL1BlockNum(mid)
if err != nil {
return nil, err
}
storedMid = mid
storedMidBlockL1Num = midBlockL1Num
if midBlockL1Num < l1BlockNum {
lowFirstBlock = mid + 1
} else {
highFirstBlock = mid
}
}
if lowLastBlock < highLastBlock {
// dont fetch midBlockL1Num if its already fetched above
mid := arbmath.SaturatingUAdd(lowLastBlock, highLastBlock) / 2
var midBlockL1Num uint64
var err error
if mid == storedMid {
midBlockL1Num = storedMidBlockL1Num
} else {
midBlockL1Num, err = n.getL1BlockNum(mid)
if err != nil {
return nil, err
}
}
if midBlockL1Num < l1BlockNum+1 {
lowLastBlock = mid + 1
} else {
highLastBlock = mid
}
}
}
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
err := checkCorrectness(highFirstBlock, l1BlockNum)
if err != nil {
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}
err = checkCorrectness(highLastBlock, l1BlockNum)
if err != nil {
highLastBlock -= 1
err = checkCorrectness(highLastBlock, l1BlockNum)
if err != nil {
return nil, err
}
}
return []uint64{highFirstBlock, highLastBlock}, nil
}
72 changes: 72 additions & 0 deletions system_tests/nodeinterface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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/ethereum/go-ethereum/ethclient"
"github.com/offchainlabs/nitro/arbos/util"
"github.com/offchainlabs/nitro/solgen/go/node_interfacegen"
)

func getL1BlockNum(t *testing.T, ctx context.Context, client *ethclient.Client, l2BlockNum uint64) uint64 {
header, err := client.HeaderByNumber(ctx, big.NewInt(int64(l2BlockNum)))
Require(t, err)
l1BlockNum := types.DeserializeHeaderExtraInformation(header).L1BlockNumber
return l1BlockNum
}
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved

func TestGetL2BlockRangeForL1(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 := 30
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][]uint64{}
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
latestL2, err := l2client.BlockNumber(ctx)
Require(t, err)
for l2BlockNum := uint64(0); l2BlockNum <= latestL2; l2BlockNum++ {
l1BlockNum := getL1BlockNum(t, ctx, l2client, l2BlockNum)
l1BlockNums[l1BlockNum] = append(l1BlockNums[l1BlockNum], l2BlockNum)
}

// Test success
for l1BlockNum := range l1BlockNums {
rng, err := nodeInterface.GetL2BlockRangeForL1(&bind.CallOpts{}, l1BlockNum)
Require(t, err)
n := len(l1BlockNums[l1BlockNum])
expected := []uint64{l1BlockNums[l1BlockNum][0], l1BlockNums[l1BlockNum][n-1]}
if expected[0] != rng[0] || expected[1] != rng[1] {
unexpectedL1BlockNum := getL1BlockNum(t, ctx, l2client, rng[1])
// handle the edge case when new l2 blocks are produced between latestL2 was last calculated and now
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
if unexpectedL1BlockNum != l1BlockNum {
t.Fatalf("GetL2BlockRangeForL1 failed to get a valid range for L1 block number: %v. Given range: %v. Expected range: %v", l1BlockNum, rng, expected)
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
// Test invalid case
finalValidL1BlockNumber := getL1BlockNum(t, ctx, l2client, latestL2)
_, err = nodeInterface.GetL2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1)
if err == nil {
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
t.Fatalf("GetL2BlockRangeForL1 didn't fail for an invalid input")
}

}
Loading