Skip to content

Commit 745f8fa

Browse files
committed
feat(evmreader): add multi binary search function
For f, x, count such that f(x) -> count. MBSearch finds the xs where count changed. To be used with GetNumberOfInputs with a block number specified in CallOpts
1 parent 85ee681 commit 745f8fa

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

internal/evmreader/util.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package evmreader
55

66
import (
77
"cmp"
8+
"fmt"
89
"slices"
910

1011
. "github.com/cartesi/rollups-node/internal/model"
@@ -64,3 +65,52 @@ func indexApps[K comparable](
6465
}
6566
return result
6667
}
68+
69+
// MBSearch is a multiple binary search over the function f.
70+
// It will find zero, one or multiple transition points x such that f(x-1) < f(x).
71+
// In addition, it will narrow the search space of subsequent points while probing f.
72+
// NOTE: This function assumes that f(0) == 0. In other words: that the transition
73+
// from 0 to 1 exists in the function image.
74+
func MBSearch(maxBlock uint64, f func(uint64) (uint64, error)) ([]uint64, error) {
75+
max, err := f(maxBlock)
76+
if err != nil {
77+
return nil, fmt.Errorf("call failed with index %v: %w", maxBlock, err)
78+
}
79+
if max == 0 {
80+
return nil, nil
81+
}
82+
83+
low := make([]uint64, max+1)
84+
high := make([]uint64, max+1)
85+
86+
for i := range max+1 {
87+
low[i] = 0
88+
high[i] = maxBlock
89+
}
90+
91+
for end := max+1; end > 1; {
92+
guess := (high[end-1] + low[end-1]) / 2
93+
index, err := f(guess)
94+
95+
if err != nil {
96+
return nil, fmt.Errorf("call failed with index %v: %w", guess, err)
97+
}
98+
99+
for i := uint64(1); i < index+1; i++ {
100+
if high[i] > guess {
101+
high[i] = guess
102+
}
103+
}
104+
105+
for i := index+1; i < end; i++ {
106+
if low[i] < guess {
107+
low[i] = guess
108+
}
109+
}
110+
111+
if low[end-1]+1 == high[end-1] {
112+
end--;
113+
}
114+
}
115+
return high[1:], nil // discard the 0 entry.
116+
}

internal/evmreader/util_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// (c) Cartesi and individual authors (see AUTHORS)
2+
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
3+
4+
package evmreader
5+
6+
import (
7+
"testing"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func fN(xs []uint64)(func(uint64)(uint64,error)) {
12+
return func(guess uint64)(uint64,error) {
13+
for i,x := range xs {
14+
if (x > guess) {
15+
return uint64(i), nil
16+
}
17+
}
18+
return uint64(len(xs)), nil
19+
}
20+
}
21+
22+
// test no values
23+
func TestMBSearch0(t *testing.T) {
24+
result, err := MBSearch(1, fN([]uint64{}))
25+
assert.Nil(t, err)
26+
assert.Equal(t, 0, len(result))
27+
}
28+
29+
// test a single values
30+
func TestMBSearch1(t *testing.T) {
31+
result, err := MBSearch(4, fN([]uint64{1}))
32+
assert.Nil(t, err)
33+
assert.Equal(t, 1, len(result))
34+
assert.Equal(t, uint64(1), result[0])
35+
}
36+
37+
// test "many" values, including repeated ones.
38+
func TestMBSearch4(t *testing.T) {
39+
result, err := MBSearch(1024, fN([]uint64{1, 100, 100, 1000}))
40+
assert.Nil(t, err)
41+
assert.Equal(t, 4, len(result))
42+
assert.Equal(t, uint64(1), result[0])
43+
assert.Equal(t, uint64(100), result[1])
44+
assert.Equal(t, uint64(100), result[2])
45+
assert.Equal(t, uint64(1000), result[3])
46+
}

0 commit comments

Comments
 (0)