Skip to content

Commit b3e5508

Browse files
committed
Fix eth_syncing checks, track nodes that are lagging behind
1 parent ae1bed8 commit b3e5508

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

internal/eth/eth.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package eth
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
// ParseHexNumber parses a hex string and returns decimal number
11+
// Note that this function does not handle large Ethereum balances in wei.
12+
func ParseHexNumber(hex string) (uint64, error) {
13+
if !strings.HasPrefix(hex, "0x") {
14+
return 0, fmt.Errorf("hex number must start with 0x")
15+
}
16+
17+
i, err := strconv.ParseUint(hex[2:], 16, 64)
18+
if err != nil {
19+
return 0, err
20+
}
21+
22+
if i == math.MaxUint64 {
23+
return 0, fmt.Errorf("number is too large")
24+
}
25+
26+
return i, nil
27+
}

internal/eth/eth_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package eth
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseHexNumber(t *testing.T) {
8+
i, _ := ParseHexNumber("0x11")
9+
10+
if i != 17 {
11+
t.Errorf("ParseHexNumber(%q) = %v, want %v", "0x11", i, 17)
12+
}
13+
14+
_, err := ParseHexNumber("0x10000000000000000000000000000000000000000000000000000000000000000")
15+
if err == nil {
16+
t.Errorf("Did not fail on out of range number")
17+
}
18+
}

internal/healthcheck.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"context"
55
"fmt"
66
"log"
7-
"strconv"
7+
"log/slog"
88
"time"
99

10+
"github.com/wille/haprovider/internal/eth"
1011
"github.com/wille/haprovider/internal/rpc"
1112
)
1213

@@ -18,6 +19,8 @@ const (
1819

1920
// Internal JSON-RPC error
2021
EthErrorInternalError = -32603
22+
23+
BlockLagTolerance = 50
2124
)
2225

2326
type Healthcheck func(ctx context.Context, p *Endpoint, e *Provider, read rpc.ReaderFunc) error
@@ -58,8 +61,24 @@ func EthereumHealthCheck(ctx context.Context, p *Endpoint, e *Provider, read rpc
5861
return err
5962
}
6063

61-
if _, ok := res.Result.(string); !ok {
62-
return fmt.Errorf("block number is not a string")
64+
if currentBlockHex, ok := res.Result.(string); ok {
65+
currentBlock, err := eth.ParseHexNumber(currentBlockHex)
66+
if err != nil {
67+
return fmt.Errorf("failed to parse block number: %v (%s)", err, currentBlockHex)
68+
}
69+
70+
if e.highestBlock != 0 && currentBlock < e.highestBlock-BlockLagTolerance {
71+
log := slog.With("provider", e.Name, "endpoint", p.Name)
72+
log.Warn("node is behind", "currentBlock", currentBlock, "highestBlock", e.highestBlock)
73+
74+
return fmt.Errorf("node is behind: currentBlock=%d, highestBlock=%d", currentBlock, e.highestBlock)
75+
}
76+
77+
if currentBlock > e.highestBlock {
78+
e.highestBlock = currentBlock
79+
}
80+
} else {
81+
return fmt.Errorf("block number is not a string: %v", res.Result)
6382
}
6483

6584
res, err = read(ctx, rpc.NewRequest("ha_syncing", "eth_syncing", nil), true)
@@ -71,8 +90,8 @@ func EthereumHealthCheck(ctx context.Context, p *Endpoint, e *Provider, read rpc
7190
currentBlockHex := m["currentBlock"].(string)
7291
highestBlockHex := m["highestBlock"].(string)
7392

74-
currentBlock, _ := strconv.ParseUint(currentBlockHex, 16, 64)
75-
highestBlock, _ := strconv.ParseUint(highestBlockHex, 16, 64)
93+
currentBlock, _ := eth.ParseHexNumber(currentBlockHex)
94+
highestBlock, _ := eth.ParseHexNumber(highestBlockHex)
7695

7796
return fmt.Errorf("node is not synced: currentBlock=%d, highestBlock=%d", currentBlock, highestBlock)
7897
}
@@ -102,7 +121,16 @@ func SolanaHealthcheck(ctx context.Context, endpoint *Endpoint, provider *Provid
102121
return err
103122
}
104123

105-
if _, ok := res.Result.(float64); !ok {
124+
if bf, ok := res.Result.(float64); !ok {
125+
blockHeight := uint64(bf)
126+
127+
if blockHeight > provider.highestBlock {
128+
provider.highestBlock = blockHeight
129+
}
130+
131+
if blockHeight < provider.highestBlock-BlockLagTolerance {
132+
return fmt.Errorf("node is behind: currentBlock=%d, highestBlock=%d", blockHeight, provider.highestBlock)
133+
}
106134
return fmt.Errorf("block height is not a number: %v", res.Result)
107135
}
108136

internal/provider.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type Provider struct {
3232
online bool
3333
onlineAt time.Time
3434

35+
highestBlock uint64
36+
3537
clientVersion string
3638

3739
m sync.Mutex
@@ -124,17 +126,8 @@ func (e *Provider) IsRateLimited() bool {
124126
return !e.retryAt.IsZero() && time.Since(e.retryAt) < 0
125127
}
126128

127-
// func (p *Provider) DelayedHealthcheck(p *Provider, delay time.Duration) error {
128-
// t := time.NewTicker(delay)
129-
// defer t.Stop()
130-
131-
// <-t.C
132-
133-
// return e.Healthcheck(p)
134-
// }
135-
136129
func (e *Provider) Healthcheck(p *Endpoint) error {
137-
ctx := context.TODO()
130+
ctx := context.Background()
138131

139132
fn := func(ctx context.Context, req *rpc.Request, errRpcError bool) (*rpc.Response, error) {
140133
res, err := SendHTTPRPCRequest(ctx, e, req)

0 commit comments

Comments
 (0)