Skip to content

Commit 55a4a2e

Browse files
fix: crawl_error detection (#57)
* Update crawler.go * added tests * added behaviour test cases
1 parent 32836e1 commit 55a4a2e

2 files changed

Lines changed: 109 additions & 9 deletions

File tree

discv5/crawler.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -504,26 +504,22 @@ func (c *Crawler) crawlDiscV5(ctx context.Context, pi PeerInfo) chan DiscV5Resul
504504
}
505505

506506
result.DoneAt = time.Now()
507-
result.Error = err
507+
// if we have at least a successful result, don't record error
508+
if noSuccessfulRequest(err, errorBits.Load()) {
509+
result.Error = err
510+
}
508511

509512
result.RoutingTable = &core.RoutingTable[PeerInfo]{
510513
PeerID: pi.ID(),
511514
Neighbors: []PeerInfo{},
512515
ErrorBits: uint16(errorBits.Load()),
513-
Error: err,
516+
Error: result.Error,
514517
}
515518

516519
for _, n := range allNeighbors {
517520
result.RoutingTable.Neighbors = append(result.RoutingTable.Neighbors, n)
518521
}
519522

520-
// if we have at least a successful result, delete error
521-
// bitwise operation checks whether errorBits is a power of 2 minus 1,
522-
// if not, then there was at least one successful result
523-
if result.Error != nil && (result.RoutingTable.ErrorBits&(result.RoutingTable.ErrorBits+1)) == 0 {
524-
result.Error = nil
525-
}
526-
527523
// if there was a connection error, parse it to a known one
528524
if result.Error != nil {
529525
result.ErrorStr = db.NetError(result.Error)
@@ -539,3 +535,16 @@ func (c *Crawler) crawlDiscV5(ctx context.Context, pi PeerInfo) chan DiscV5Resul
539535

540536
return resultCh
541537
}
538+
539+
// noSuccessfulRequest returns true if the given error is non nil, and all bits
540+
// of the given errorBits are set. This means that no successful request has
541+
// been made. This is equivalent to verifying that all righmost bits are equal
542+
// to 1, or that the errorBits is a power of 2 minus 1.
543+
//
544+
// Examples:
545+
// 0b00000011 -> true
546+
// 0b00000111 -> true
547+
// 0b00001101 -> false
548+
func noSuccessfulRequest(err error, errorBits uint32) bool {
549+
return err != nil && errorBits&(errorBits+1) == 0
550+
}

discv5/crawler_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package discv5
22

33
import (
4+
"errors"
5+
"fmt"
6+
"math"
47
"testing"
58

69
ma "github.com/multiformats/go-multiaddr"
710
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
812

913
"github.com/dennis-tra/nebula-crawler/nebtest"
1014
)
@@ -98,3 +102,90 @@ func Test_sanitizeAddrs(t *testing.T) {
98102
})
99103
}
100104
}
105+
106+
func TestNoSuccessfulRequest(t *testing.T) {
107+
tests := []struct {
108+
name string
109+
err error
110+
errorBits uint32
111+
want bool
112+
}{
113+
{
114+
name: "no err",
115+
err: nil,
116+
errorBits: 0b00000000,
117+
want: false,
118+
},
119+
{
120+
name: "first failed",
121+
err: fmt.Errorf("some err"),
122+
errorBits: 0b00000001,
123+
want: true,
124+
},
125+
{
126+
name: "second failed, first worked",
127+
err: fmt.Errorf("some err"),
128+
errorBits: 0b00000010,
129+
want: false,
130+
},
131+
{
132+
name: "all four failed",
133+
err: fmt.Errorf("some err"),
134+
errorBits: 0b00001111,
135+
want: true,
136+
},
137+
{
138+
name: "seven failed, one worked",
139+
err: fmt.Errorf("some err"),
140+
errorBits: 0b11110111,
141+
want: false,
142+
},
143+
{
144+
name: "eight failed, but the last one overflowing succeeded (no error)",
145+
err: nil,
146+
errorBits: 0b11111111,
147+
want: false,
148+
},
149+
}
150+
for _, tt := range tests {
151+
t.Run(tt.name, func(t *testing.T) {
152+
if got := noSuccessfulRequest(tt.err, tt.errorBits); got != tt.want {
153+
t.Errorf("noSuccessfulRequest() = %v, want %v, %s", got, tt.want, tt.name)
154+
}
155+
})
156+
}
157+
158+
// fail if err is nil
159+
require.False(t, noSuccessfulRequest(nil, 0))
160+
require.False(t, noSuccessfulRequest(nil, 0b11111111))
161+
162+
err := errors.New("error")
163+
164+
// list of numbers that are power of two minus one
165+
// for which noSuccessfulRequest should return true
166+
// because all bits are set (all failures = no success)
167+
powerOfTwoMinusOneList := []uint32{
168+
0b00000000,
169+
0b00000001,
170+
0b00000011,
171+
0b00000111,
172+
0b00001111,
173+
0b00011111,
174+
0b00111111,
175+
0b01111111,
176+
0b11111111,
177+
}
178+
179+
for i := uint32(0); i < uint32(math.Pow(2, 8)); i++ {
180+
powerOfTwoMinusOne := false
181+
for _, v := range powerOfTwoMinusOneList {
182+
if i == v {
183+
powerOfTwoMinusOne = true
184+
break
185+
}
186+
}
187+
// assert that noSuccessfulRequest returns true if and only if
188+
// all bits are set
189+
require.Equal(t, powerOfTwoMinusOne, noSuccessfulRequest(err, i))
190+
}
191+
}

0 commit comments

Comments
 (0)