From efc4fc134d97b55f9b5f2fd91a7fb1e8c1021f5a Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Wed, 13 Sep 2023 11:08:10 -0500 Subject: [PATCH 1/7] add new RPC GetL2BlockRangeForL1 to fetch L2 block range for L1 block number --- contracts | 2 +- go-ethereum | 2 +- nodeInterface/NodeInterface.go | 80 ++++++++++++++++++++++++++++++ system_tests/nodeinterface_test.go | 72 +++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 system_tests/nodeinterface_test.go diff --git a/contracts b/contracts index 97cfbe00ff..accdcee457 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 97cfbe00ff0eea4d7f5f5f3afb01598c19ddabc4 +Subproject commit accdcee45798af5025836a04ee5bdcb0669cb476 diff --git a/go-ethereum b/go-ethereum index b4bd0da114..3f2e789b38 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit b4bd0da1142fe6bb81cac7e0794ebb4746b9885a +Subproject commit 3f2e789b3857ccdd647c319e16f1a00805d1d6bd diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index a363458663..3b743dbb2d 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -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) { + 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) { + currentBlockNum := n.backend.CurrentBlock().Number.Uint64() + genesis := n.backend.ChainConfig().ArbitrumChainParams.GenesisBlockNum + + checkCorrectness := func(blockNum uint64, target uint64) error { + blockL1Num, err := n.getL1BlockNum(blockNum) + if err != nil { + return err + } + if blockL1Num != target { + return errors.New("no L2 block was found with the given L1 block number") + } + return nil + } + + lowFirstBlock := genesis + highFirstBlock := currentBlockNum + lowLastBlock := genesis + highLastBlock := currentBlockNum + var storedMid uint64 + var storedMidBlockL1Num uint64 + for lowFirstBlock < highFirstBlock || lowLastBlock < highLastBlock { + if lowFirstBlock < highFirstBlock { + mid := (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 := (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 + } + } + } + err := checkCorrectness(highFirstBlock, l1BlockNum) + if err != nil { + 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 +} diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go new file mode 100644 index 0000000000..266b50d6c8 --- /dev/null +++ b/system_tests/nodeinterface_test.go @@ -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 +} + +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{} + 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 + 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) + } + } + } + // Test invalid case + finalValidL1BlockNumber := getL1BlockNum(t, ctx, l2client, latestL2) + _, err = nodeInterface.GetL2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1) + if err == nil { + t.Fatalf("GetL2BlockRangeForL1 didn't fail for an invalid input") + } + +} From 3941ebc9e07bca3f5be9a9c3abe2816b8665312b Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Fri, 15 Sep 2023 14:02:42 -0500 Subject: [PATCH 2/7] fix overflow possibility --- nodeInterface/NodeInterface.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index 3b743dbb2d..92ed2064c3 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -623,7 +623,7 @@ func (n NodeInterface) GetL2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) var storedMidBlockL1Num uint64 for lowFirstBlock < highFirstBlock || lowLastBlock < highLastBlock { if lowFirstBlock < highFirstBlock { - mid := (lowFirstBlock + highFirstBlock) / 2 + mid := arbmath.SaturatingUAdd(lowFirstBlock, highFirstBlock) / 2 midBlockL1Num, err := n.getL1BlockNum(mid) if err != nil { return nil, err @@ -638,7 +638,7 @@ func (n NodeInterface) GetL2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) } if lowLastBlock < highLastBlock { // dont fetch midBlockL1Num if its already fetched above - mid := (lowLastBlock + highLastBlock) / 2 + mid := arbmath.SaturatingUAdd(lowLastBlock, highLastBlock) / 2 var midBlockL1Num uint64 var err error if mid == storedMid { From b5379b9eba7b847bdb64cdb6c0bb2640bbe86211 Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Mon, 18 Sep 2023 16:06:02 -0500 Subject: [PATCH 3/7] address PR comments --- contracts | 2 +- nodeInterface/NodeInterface.go | 106 ++++++++++++++--------------- system_tests/nodeinterface_test.go | 43 ++++++------ 3 files changed, 77 insertions(+), 74 deletions(-) diff --git a/contracts b/contracts index accdcee457..436e1cf82c 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit accdcee45798af5025836a04ee5bdcb0669cb476 +Subproject commit 436e1cf82c5696eb918d842256328ba86fbe5019 diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index 92ed2064c3..98394f9343 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -591,82 +591,82 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h return } -func (n NodeInterface) getL1BlockNum(l2BlockNum uint64) (uint64, error) { +func (n NodeInterface) blockL1Num(l2BlockNum uint64) (uint64, error) { blockHeader, err := n.backend.HeaderByNumber(n.context, rpc.BlockNumber(l2BlockNum)) if err != nil { return 0, err } - l1BlockNum := types.DeserializeHeaderExtraInformation(blockHeader).L1BlockNumber - return l1BlockNum, nil + blockL1Num := types.DeserializeHeaderExtraInformation(blockHeader).L1BlockNumber + return blockL1Num, nil } -func (n NodeInterface) GetL2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) ([]uint64, error) { +func (n NodeInterface) matchL2BlockNumWithL1(l2BlockNum uint64, l1BlockNum uint64) error { + blockL1Num, err := n.blockL1Num(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 - checkCorrectness := func(blockNum uint64, target uint64) error { - blockL1Num, err := n.getL1BlockNum(blockNum) - if err != nil { - return err - } - if blockL1Num != target { - return errors.New("no L2 block was found with the given L1 block number") - } - return nil + type helperStruct struct { + low uint64 + high uint64 } - 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 { + searchHelper := func(currentBlock *helperStruct, fetchedMid *helperStruct, target uint64) error { + if currentBlock.low < currentBlock.high { // dont fetch midBlockL1Num if its already fetched above - mid := arbmath.SaturatingUAdd(lowLastBlock, highLastBlock) / 2 + mid := arbmath.SaturatingUAdd(currentBlock.low, currentBlock.high) / 2 var midBlockL1Num uint64 var err error - if mid == storedMid { - midBlockL1Num = storedMidBlockL1Num + if mid == fetchedMid.low { + midBlockL1Num = fetchedMid.high } else { - midBlockL1Num, err = n.getL1BlockNum(mid) + midBlockL1Num, err = n.blockL1Num(mid) if err != nil { - return nil, err + return err } + fetchedMid.low = mid + fetchedMid.high = midBlockL1Num } - if midBlockL1Num < l1BlockNum+1 { - lowLastBlock = mid + 1 + if midBlockL1Num < target { + currentBlock.low = mid + 1 } else { - highLastBlock = mid + currentBlock.high = mid } + return nil } + return nil } - err := checkCorrectness(highFirstBlock, l1BlockNum) - if err != nil { - return nil, err + firstBlock := &helperStruct{low: genesis, high: currentBlockNum} + lastBlock := &helperStruct{low: genesis, high: currentBlockNum} + // in storedMid low corresponds to value mid and high corresponds to midBlockL1Num inside searchHelper + storedMid := &helperStruct{low: currentBlockNum + 1} + var err error + for firstBlock.low < firstBlock.high || lastBlock.low < lastBlock.high { + if err = searchHelper(firstBlock, storedMid, l1BlockNum); err != nil { + return 0, 0, err + } + if err = searchHelper(lastBlock, storedMid, l1BlockNum+1); err != nil { + return 0, 0, err + } } - err = checkCorrectness(highLastBlock, l1BlockNum) - if err != nil { - highLastBlock -= 1 - err = checkCorrectness(highLastBlock, l1BlockNum) - if err != nil { - return nil, err + if err := n.matchL2BlockNumWithL1(firstBlock.high, l1BlockNum); err != nil { + return 0, 0, err + } + if err := n.matchL2BlockNumWithL1(lastBlock.high, l1BlockNum); err != nil { + lastBlock.high -= 1 + if err = n.matchL2BlockNumWithL1(lastBlock.high, l1BlockNum); err != nil { + return 0, 0, err } } - return []uint64{highFirstBlock, highLastBlock}, nil + return firstBlock.high, lastBlock.high, nil } diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index 266b50d6c8..bfdff3d02d 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -10,19 +10,11 @@ import ( "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 -} - -func TestGetL2BlockRangeForL1(t *testing.T) { +func TestL2BlockRangeForL1(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -32,7 +24,7 @@ func TestGetL2BlockRangeForL1(t *testing.T) { defer node.StopAndWait() user := l1info.GetDefaultTransactOpts("User", ctx) - numTransactions := 30 + numTransactions := 200 for i := 0; i < numTransactions; i++ { TransferBalanceTo(t, "Owner", util.RemapL1Address(user.From), big.NewInt(1e18), l2info, l2client, ctx) } @@ -40,31 +32,42 @@ func TestGetL2BlockRangeForL1(t *testing.T) { nodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, l2client) Require(t, err) + getBlockL1Num := func(l2BlockNum uint64) uint64 { + header, err := l2client.HeaderByNumber(ctx, big.NewInt(int64(l2BlockNum))) + Require(t, err) + l1BlockNum := types.DeserializeHeaderExtraInformation(header).L1BlockNumber + return l1BlockNum + } + l1BlockNums := map[uint64][]uint64{} 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) + l1BlockNum := getBlockL1Num(l2BlockNum) + if len(l1BlockNums[l1BlockNum]) <= 1 { + l1BlockNums[l1BlockNum] = append(l1BlockNums[l1BlockNum], l2BlockNum) + } else { + l1BlockNums[l1BlockNum][1] = l2BlockNum + } } // Test success for l1BlockNum := range l1BlockNums { - rng, err := nodeInterface.GetL2BlockRangeForL1(&bind.CallOpts{}, l1BlockNum) + rng, err := nodeInterface.L2BlockRangeForL1(&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 - 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) + if expected[0] != rng.FirstBlock || expected[1] != rng.LastBlock { + unexpectedL1BlockNum := getBlockL1Num(rng.LastBlock) + // Handle the edge case when new l2 blocks are produced between latestL2 was last calculated and now. + if unexpectedL1BlockNum != l1BlockNum || rng.LastBlock < expected[1] { + t.Errorf("L2BlockRangeForL1(%d) = (%d %d) want (%d %d)", l1BlockNum, rng.FirstBlock, rng.LastBlock, expected[0], expected[1]) } } } // Test invalid case - finalValidL1BlockNumber := getL1BlockNum(t, ctx, l2client, latestL2) - _, err = nodeInterface.GetL2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1) + finalValidL1BlockNumber := getBlockL1Num(latestL2) + _, err = nodeInterface.L2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1) if err == nil { t.Fatalf("GetL2BlockRangeForL1 didn't fail for an invalid input") } From 9b2788f8dfd6f915f27b2f10323f809d8d34b477 Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Wed, 20 Sep 2023 11:05:34 -0500 Subject: [PATCH 4/7] address PR comments --- contracts | 2 +- nodeInterface/NodeInterface.go | 75 ++++++++++++------------------ system_tests/nodeinterface_test.go | 34 ++++++-------- 3 files changed, 46 insertions(+), 65 deletions(-) diff --git a/contracts b/contracts index 436e1cf82c..9edc1b943e 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 436e1cf82c5696eb918d842256328ba86fbe5019 +Subproject commit 9edc1b943ed0255f050f91f265d96bc1ad9de1a2 diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index 98394f9343..e990383a3b 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -591,7 +591,7 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h return } -func (n NodeInterface) blockL1Num(l2BlockNum uint64) (uint64, error) { +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 @@ -600,8 +600,8 @@ func (n NodeInterface) blockL1Num(l2BlockNum uint64) (uint64, error) { return blockL1Num, nil } -func (n NodeInterface) matchL2BlockNumWithL1(l2BlockNum uint64, l1BlockNum uint64) error { - blockL1Num, err := n.blockL1Num(l2BlockNum) +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) } @@ -616,57 +616,44 @@ func (n NodeInterface) L2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) (ui currentBlockNum := n.backend.CurrentBlock().Number.Uint64() genesis := n.backend.ChainConfig().ArbitrumChainParams.GenesisBlockNum - type helperStruct struct { - low uint64 - high uint64 - } - - searchHelper := func(currentBlock *helperStruct, fetchedMid *helperStruct, target uint64) error { - if currentBlock.low < currentBlock.high { - // dont fetch midBlockL1Num if its already fetched above - mid := arbmath.SaturatingUAdd(currentBlock.low, currentBlock.high) / 2 - var midBlockL1Num uint64 - var err error - if mid == fetchedMid.low { - midBlockL1Num = fetchedMid.high - } else { - midBlockL1Num, err = n.blockL1Num(mid) + storedMids := map[uint64]uint64{} + firstL2BlockForL1 := func(target uint64) (uint64, error) { + low, high := genesis, currentBlockNum + 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 err + return 0, err } - fetchedMid.low = mid - fetchedMid.high = midBlockL1Num + storedMids[mid] = midBlockL1Num } - if midBlockL1Num < target { - currentBlock.low = mid + 1 + if storedMids[mid] < target { + low = mid + 1 } else { - currentBlock.high = mid + high = mid } - return nil - } - return nil - } - firstBlock := &helperStruct{low: genesis, high: currentBlockNum} - lastBlock := &helperStruct{low: genesis, high: currentBlockNum} - // in storedMid low corresponds to value mid and high corresponds to midBlockL1Num inside searchHelper - storedMid := &helperStruct{low: currentBlockNum + 1} - var err error - for firstBlock.low < firstBlock.high || lastBlock.low < lastBlock.high { - if err = searchHelper(firstBlock, storedMid, l1BlockNum); err != nil { - return 0, 0, err - } - if err = searchHelper(lastBlock, storedMid, l1BlockNum+1); err != nil { - return 0, 0, err } + return high, nil } - if err := n.matchL2BlockNumWithL1(firstBlock.high, l1BlockNum); err != nil { + + firstBlock, err := firstL2BlockForL1(l1BlockNum) + if err != nil { + return 0, 0, err + } + lastBlock, err := firstL2BlockForL1(l1BlockNum + 1) + if err != nil { + return 0, 0, err + } + + if err := n.matchL2BlockNumWithL1(c, evm, firstBlock, l1BlockNum); err != nil { return 0, 0, err } - if err := n.matchL2BlockNumWithL1(lastBlock.high, l1BlockNum); err != nil { - lastBlock.high -= 1 - if err = n.matchL2BlockNumWithL1(lastBlock.high, l1BlockNum); err != nil { + if err := n.matchL2BlockNumWithL1(c, evm, lastBlock, l1BlockNum); err != nil { + lastBlock -= 1 + if err = n.matchL2BlockNumWithL1(c, evm, lastBlock, l1BlockNum); err != nil { return 0, 0, err } } - return firstBlock.high, lastBlock.high, nil + return firstBlock, lastBlock, nil } diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index bfdff3d02d..3389dda7c9 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -32,22 +32,16 @@ func TestL2BlockRangeForL1(t *testing.T) { nodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, l2client) Require(t, err) - getBlockL1Num := func(l2BlockNum uint64) uint64 { - header, err := l2client.HeaderByNumber(ctx, big.NewInt(int64(l2BlockNum))) - Require(t, err) - l1BlockNum := types.DeserializeHeaderExtraInformation(header).L1BlockNumber - return l1BlockNum - } - - l1BlockNums := map[uint64][]uint64{} + l1BlockNums := map[uint64][2]uint64{} latestL2, err := l2client.BlockNumber(ctx) Require(t, err) for l2BlockNum := uint64(0); l2BlockNum <= latestL2; l2BlockNum++ { - l1BlockNum := getBlockL1Num(l2BlockNum) - if len(l1BlockNums[l1BlockNum]) <= 1 { - l1BlockNums[l1BlockNum] = append(l1BlockNums[l1BlockNum], 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 + l1BlockNums[l1BlockNum] = [2]uint64{l1BlockNums[l1BlockNum][0], l2BlockNum} } } @@ -55,20 +49,20 @@ func TestL2BlockRangeForL1(t *testing.T) { for l1BlockNum := range l1BlockNums { rng, err := nodeInterface.L2BlockRangeForL1(&bind.CallOpts{}, l1BlockNum) Require(t, err) - n := len(l1BlockNums[l1BlockNum]) - expected := []uint64{l1BlockNums[l1BlockNum][0], l1BlockNums[l1BlockNum][n-1]} - if expected[0] != rng.FirstBlock || expected[1] != rng.LastBlock { - unexpectedL1BlockNum := getBlockL1Num(rng.LastBlock) + 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] { + 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 := getBlockL1Num(latestL2) - _, err = nodeInterface.L2BlockRangeForL1(&bind.CallOpts{}, finalValidL1BlockNumber+1) - if err == nil { + 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") } From 994d0b5b9a640e8cc745c39a24373a5127f867d7 Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Thu, 21 Sep 2023 09:17:47 -0500 Subject: [PATCH 5/7] code refactor --- nodeInterface/NodeInterface.go | 4 ++-- system_tests/nodeinterface_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index e990383a3b..2a45ac033d 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -639,11 +639,11 @@ func (n NodeInterface) L2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) (ui firstBlock, err := firstL2BlockForL1(l1BlockNum) if err != nil { - return 0, 0, err + 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, err + 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 { diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index 3389dda7c9..63b3d7bb7b 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -32,16 +32,16 @@ func TestL2BlockRangeForL1(t *testing.T) { nodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, l2client) Require(t, err) - l1BlockNums := map[uint64][2]uint64{} + 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} + l1BlockNums[l1BlockNum] = &[2]uint64{l2BlockNum, l2BlockNum} } else { - l1BlockNums[l1BlockNum] = [2]uint64{l1BlockNums[l1BlockNum][0], l2BlockNum} + l1BlockNums[l1BlockNum][1] = l2BlockNum } } From cb10050cbbee58b228055c1454744670121613fb Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Fri, 22 Sep 2023 09:33:45 -0500 Subject: [PATCH 6/7] code refactor --- nodeInterface/NodeInterface.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index 2a45ac033d..f114cd5ac9 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -591,6 +591,7 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h 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) { blockHeader, err := n.backend.HeaderByNumber(n.context, rpc.BlockNumber(l2BlockNum)) if err != nil { @@ -619,6 +620,13 @@ func (n NodeInterface) L2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) (ui 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 { @@ -649,11 +657,9 @@ func (n NodeInterface) L2BlockRangeForL1(c ctx, evm mech, l1BlockNum uint64) (ui if err := n.matchL2BlockNumWithL1(c, evm, firstBlock, l1BlockNum); err != nil { return 0, 0, err } - if err := n.matchL2BlockNumWithL1(c, evm, lastBlock, l1BlockNum); err != nil { - lastBlock -= 1 - if err = n.matchL2BlockNumWithL1(c, evm, lastBlock, 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 } From 5bd76f333be3fdd54fd48db02723f8b84726daf1 Mon Sep 17 00:00:00 2001 From: ganeshvanahalli Date: Mon, 25 Sep 2023 10:13:48 -0500 Subject: [PATCH 7/7] add documentation --- nodeInterface/NodeInterface.go | 1 + 1 file changed, 1 insertion(+) diff --git a/nodeInterface/NodeInterface.go b/nodeInterface/NodeInterface.go index f114cd5ac9..6984255393 100644 --- a/nodeInterface/NodeInterface.go +++ b/nodeInterface/NodeInterface.go @@ -592,6 +592,7 @@ func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum h } // 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 {