Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

Commit 6d60696

Browse files
authored
Use a constant amount of memory for error slices and add error abstraction (#57)
This PR changes the Info struct in the Ingester a bit so that we have an inner Errors struct for keeping track of errors. We add a method for resetting at, for observing errors, and for transforming to progress reports. We make sure that the error slices do not grow unbounded, but we keep track of the total error counts as well.
1 parent e247c53 commit 6d60696

8 files changed

Lines changed: 190 additions & 69 deletions

File tree

client/duneapi/client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ func (c *client) PostProgressReport(ctx context.Context, progress models.Blockch
260260
LastIngestedBlockNumber: progress.LastIngestedBlockNumber,
261261
LatestBlockNumber: progress.LatestBlockNumber,
262262
Errors: errors,
263+
DuneErrorCounts: progress.DuneErrorCounts,
264+
RPCErrorCounts: progress.RPCErrorCounts,
265+
Since: progress.Since,
263266
}
264267
url := fmt.Sprintf("%s/api/beta/blockchain/%s/ingest/progress", c.cfg.URL, c.cfg.BlockchainName)
265268
payload, err := json.Marshal(request)

client/duneapi/models.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ func (p *GetBlockchainProgressResponse) String() string {
7171
type PostBlockchainProgressRequest struct {
7272
LastIngestedBlockNumber int64 `json:"last_ingested_block_number,omitempty"`
7373
LatestBlockNumber int64 `json:"latest_block_number,omitempty"`
74-
Errors []BlockchainError `json:"errors,omitempty"`
74+
Errors []BlockchainError `json:"errors"`
75+
DuneErrorCounts int `json:"dune_error_counts"`
76+
RPCErrorCounts int `json:"rpc_error_counts"`
77+
Since time.Time `json:"since"`
7578
}
7679

7780
type BlockchainError struct {

ingester/ingester.go

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,42 +52,6 @@ type Config struct {
5252
SkipFailedBlocks bool
5353
}
5454

55-
type Info struct {
56-
LatestBlockNumber int64
57-
IngestedBlockNumber int64
58-
ConsumedBlockNumber int64
59-
RPCErrors []ErrorInfo
60-
DuneErrors []ErrorInfo
61-
}
62-
63-
// Errors returns a combined list of errors from RPC requests and Dune requests, for use in progress reporting
64-
func (info Info) Errors() []models.BlockchainIndexError {
65-
errors := make([]models.BlockchainIndexError, 0, len(info.RPCErrors)+len(info.DuneErrors))
66-
for _, e := range info.RPCErrors {
67-
errors = append(errors, models.BlockchainIndexError{
68-
Timestamp: e.Timestamp,
69-
BlockNumbers: e.BlockNumbers,
70-
Error: e.Error.Error(),
71-
Source: "rpc",
72-
})
73-
}
74-
for _, e := range info.DuneErrors {
75-
errors = append(errors, models.BlockchainIndexError{
76-
Timestamp: e.Timestamp,
77-
BlockNumbers: e.BlockNumbers,
78-
Error: e.Error.Error(),
79-
Source: "dune",
80-
})
81-
}
82-
return errors
83-
}
84-
85-
type ErrorInfo struct {
86-
Timestamp time.Time
87-
BlockNumbers string
88-
Error error
89-
}
90-
9155
type ingester struct {
9256
log *slog.Logger
9357
node jsonrpc.BlockchainClient
@@ -103,10 +67,7 @@ func New(
10367
cfg Config,
10468
progress *models.BlockchainIndexProgress,
10569
) Ingester {
106-
info := Info{
107-
RPCErrors: []ErrorInfo{},
108-
DuneErrors: []ErrorInfo{},
109-
}
70+
info := NewInfo(cfg.BlockchainName, cfg.Stack.String())
11071
if progress != nil {
11172
info.LatestBlockNumber = progress.LatestBlockNumber
11273
info.IngestedBlockNumber = progress.LastIngestedBlockNumber

ingester/mainloop.go

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,6 @@ func (i *ingester) ReportProgress(ctx context.Context) error {
188188
blocksPerSec := float64(lastIngested-previousIngested) / tNow.Sub(previousTime).Seconds()
189189
newDistance := latest - lastIngested
190190

191-
rpcErrors := len(i.info.RPCErrors)
192-
duneErrors := len(i.info.DuneErrors)
193191
fields := []interface{}{
194192
"blocksPerSec", fmt.Sprintf("%.2f", blocksPerSec),
195193
"latestBlockNumber", latest,
@@ -203,31 +201,23 @@ func (i *ingester) ReportProgress(ctx context.Context) error {
203201
}
204202
previousHoursToCatchUp = etaHours
205203
}
206-
if rpcErrors > 0 {
207-
fields = append(fields, "rpcErrors", rpcErrors)
204+
if i.info.Errors.RPCErrorCount > 0 {
205+
fields = append(fields, "rpcErrors", i.info.Errors.RPCErrorCount)
208206
}
209-
if duneErrors > 0 {
210-
fields = append(fields, "duneErrors", duneErrors)
207+
if i.info.Errors.DuneErrorCount > 0 {
208+
fields = append(fields, "duneErrors", i.info.Errors.DuneErrorCount)
211209
}
212210

213211
i.log.Info("PROGRESS REPORT", fields...)
214212
previousIngested = lastIngested
215213
previousTime = tNow
216214

217-
err := i.dune.PostProgressReport(ctx, models.BlockchainIndexProgress{
218-
BlockchainName: i.cfg.BlockchainName,
219-
EVMStack: i.cfg.Stack.String(),
220-
LastIngestedBlockNumber: lastIngested,
221-
LatestBlockNumber: latest,
222-
Errors: i.info.Errors(),
223-
})
215+
err := i.dune.PostProgressReport(ctx, i.info.ToProgressReport())
224216
if err != nil {
225217
i.log.Error("Failed to post progress report", "error", err)
226218
} else {
227219
i.log.Debug("Posted progress report")
228-
// Reset errors after reporting
229-
i.info.RPCErrors = []ErrorInfo{}
230-
i.info.DuneErrors = []ErrorInfo{}
220+
i.info.ResetErrors()
231221
}
232222
}
233223
}
@@ -238,14 +228,7 @@ func (i *ingester) Close() error {
238228
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
239229
defer cancel()
240230
i.log.Info("Sending final progress report")
241-
err := i.dune.PostProgressReport(
242-
ctx,
243-
models.BlockchainIndexProgress{
244-
BlockchainName: i.cfg.BlockchainName,
245-
EVMStack: i.cfg.Stack.String(),
246-
LastIngestedBlockNumber: i.info.IngestedBlockNumber,
247-
LatestBlockNumber: i.info.LatestBlockNumber,
248-
})
231+
err := i.dune.PostProgressReport(ctx, i.info.ToProgressReport())
249232
i.log.Info("Closing node")
250233
if err != nil {
251234
_ = i.node.Close()

ingester/models.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package ingester
2+
3+
import (
4+
"time"
5+
6+
"github.com/duneanalytics/blockchain-ingester/models"
7+
)
8+
9+
type Info struct {
10+
BlockchainName string
11+
Stack string
12+
LatestBlockNumber int64
13+
IngestedBlockNumber int64
14+
ConsumedBlockNumber int64
15+
Errors ErrorState
16+
Since time.Time
17+
}
18+
19+
func NewInfo(blockchain string, stack string) Info {
20+
return Info{
21+
BlockchainName: blockchain,
22+
Stack: stack,
23+
Errors: ErrorState{
24+
RPCErrors: make([]ErrorInfo, 0, 100),
25+
DuneErrors: make([]ErrorInfo, 0, 100),
26+
RPCErrorCount: 0,
27+
DuneErrorCount: 0,
28+
},
29+
Since: time.Now(),
30+
}
31+
}
32+
33+
func (info *Info) ToProgressReport() models.BlockchainIndexProgress {
34+
return models.BlockchainIndexProgress{
35+
BlockchainName: info.BlockchainName,
36+
EVMStack: info.Stack,
37+
LastIngestedBlockNumber: info.IngestedBlockNumber,
38+
LatestBlockNumber: info.LatestBlockNumber,
39+
Errors: info.ProgressReportErrors(),
40+
DuneErrorCounts: info.Errors.DuneErrorCount,
41+
RPCErrorCounts: info.Errors.RPCErrorCount,
42+
Since: info.Since,
43+
}
44+
}
45+
46+
func (info *Info) ResetErrors() {
47+
info.Since = time.Now()
48+
info.Errors.Reset()
49+
}
50+
51+
type ErrorState struct {
52+
RPCErrors []ErrorInfo
53+
DuneErrors []ErrorInfo
54+
RPCErrorCount int
55+
DuneErrorCount int
56+
}
57+
58+
// ProgressReportErrors returns a combined list of errors from RPC requests and Dune requests
59+
func (info Info) ProgressReportErrors() []models.BlockchainIndexError {
60+
errors := make([]models.BlockchainIndexError, 0, len(info.Errors.RPCErrors)+len(info.Errors.DuneErrors))
61+
for _, e := range info.Errors.RPCErrors {
62+
errors = append(errors, models.BlockchainIndexError{
63+
Timestamp: e.Timestamp,
64+
BlockNumbers: e.BlockNumbers,
65+
Error: e.Error.Error(),
66+
Source: "rpc",
67+
})
68+
}
69+
for _, e := range info.Errors.DuneErrors {
70+
errors = append(errors, models.BlockchainIndexError{
71+
Timestamp: e.Timestamp,
72+
BlockNumbers: e.BlockNumbers,
73+
Error: e.Error.Error(),
74+
Source: "dune",
75+
})
76+
}
77+
return errors
78+
}
79+
80+
func (es *ErrorState) Reset() {
81+
es.RPCErrors = es.RPCErrors[:0]
82+
es.DuneErrors = es.DuneErrors[:0]
83+
es.RPCErrorCount = 0
84+
es.DuneErrorCount = 0
85+
}
86+
87+
func (es *ErrorState) ObserveRPCError(err ErrorInfo) {
88+
es.RPCErrorCount++
89+
err.Timestamp = time.Now()
90+
91+
// If we have filled the slice, remove the oldest error
92+
if len(es.RPCErrors) == cap(es.RPCErrors) {
93+
tmp := make([]ErrorInfo, len(es.RPCErrors)-1, cap(es.RPCErrors))
94+
copy(tmp, es.RPCErrors[1:])
95+
es.RPCErrors = tmp
96+
}
97+
es.RPCErrors = append(es.RPCErrors, err)
98+
}
99+
100+
func (es *ErrorState) ObserveDuneError(err ErrorInfo) {
101+
es.DuneErrorCount++
102+
err.Timestamp = time.Now()
103+
104+
// If we have filled the slice, remove the oldest error
105+
if len(es.DuneErrors) == cap(es.DuneErrors) {
106+
tmp := make([]ErrorInfo, len(es.DuneErrors)-1, cap(es.DuneErrors))
107+
copy(tmp, es.DuneErrors[1:])
108+
es.DuneErrors = tmp
109+
}
110+
es.DuneErrors = append(es.DuneErrors, err)
111+
}
112+
113+
type ErrorInfo struct {
114+
Timestamp time.Time
115+
BlockNumbers string
116+
Error error
117+
}

ingester/models_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package ingester_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/duneanalytics/blockchain-ingester/ingester"
7+
"github.com/go-errors/errors"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// TestInfoErrors ensures that we never allow the error slices to grow indefinitely
12+
func TestInfoErrors(t *testing.T) {
13+
info := ingester.NewInfo("test", "test")
14+
for j := 0; j < 2; j++ {
15+
for i := 0; i < 200; i++ {
16+
require.Len(t, info.Errors.RPCErrors, min(i, 100))
17+
require.Len(t, info.Errors.DuneErrors, min(i, 100))
18+
info.Errors.ObserveDuneError(ingester.ErrorInfo{})
19+
info.Errors.ObserveRPCError(ingester.ErrorInfo{})
20+
require.Equal(t, 100, cap(info.Errors.RPCErrors))
21+
require.Equal(t, 100, cap(info.Errors.DuneErrors))
22+
}
23+
info.ResetErrors()
24+
require.Len(t, info.Errors.RPCErrors, 0)
25+
require.Len(t, info.Errors.DuneErrors, 0)
26+
require.Equal(t, 100, cap(info.Errors.RPCErrors))
27+
require.Equal(t, 100, cap(info.Errors.DuneErrors))
28+
}
29+
}
30+
31+
func TestProgressReportErrors(t *testing.T) {
32+
info := ingester.NewInfo("test", "test")
33+
info.Errors.ObserveDuneError(ingester.ErrorInfo{Error: errors.New("foo")})
34+
info.Errors.ObserveRPCError(ingester.ErrorInfo{Error: errors.New("bar")})
35+
errors := info.ProgressReportErrors()
36+
require.Len(t, errors, 2)
37+
}
38+
39+
func TestInfoToProgressReport(t *testing.T) {
40+
info := ingester.NewInfo("test", "test")
41+
info.IngestedBlockNumber = 1
42+
info.LatestBlockNumber = 2
43+
info.Errors.ObserveDuneError(ingester.ErrorInfo{Error: errors.New("foo")})
44+
report := info.ToProgressReport()
45+
require.Equal(t, "test", report.BlockchainName)
46+
require.Equal(t, "test", report.EVMStack)
47+
require.Equal(t, int64(1), report.LastIngestedBlockNumber)
48+
require.Equal(t, int64(2), report.LatestBlockNumber)
49+
require.Len(t, report.Errors, 1)
50+
require.Equal(t, 1, report.DuneErrorCounts)
51+
require.Equal(t, 0, report.RPCErrorCounts)
52+
}

ingester/send.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ func (i *ingester) SendBlocks(ctx context.Context, blocks <-chan models.RPCBlock
3636
}
3737

3838
if block.Errored() {
39-
i.info.RPCErrors = append(i.info.RPCErrors, ErrorInfo{
40-
Timestamp: time.Now(),
39+
i.info.Errors.ObserveRPCError(ErrorInfo{
4140
BlockNumbers: fmt.Sprintf("%d", block.BlockNumber),
4241
Error: block.Error,
4342
})
@@ -125,8 +124,8 @@ func (i *ingester) trySendBlockBatch(
125124
for i, block := range blockBatch {
126125
blocknumbers[i] = fmt.Sprintf("%d", block.BlockNumber)
127126
}
128-
i.info.DuneErrors = append(i.info.DuneErrors, ErrorInfo{
129-
Timestamp: time.Now(),
127+
128+
i.info.Errors.ObserveDuneError(ErrorInfo{
130129
Error: err,
131130
BlockNumbers: strings.Join(blocknumbers, ","),
132131
})

models/progress.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ type BlockchainIndexProgress struct {
1010
LastIngestedBlockNumber int64
1111
LatestBlockNumber int64
1212
Errors []BlockchainIndexError
13+
DuneErrorCounts int
14+
RPCErrorCounts int
15+
Since time.Time
1316
}
1417

1518
type BlockchainIndexError struct {

0 commit comments

Comments
 (0)