Skip to content

Commit eb603e4

Browse files
committed
blockchain/indexers: propagate index drop errors
In this commit, we propagate errors returned while cataloging index buckets, matching the bucket deletion path fixed by the preceding commit. We also add fault-injected regression coverage for both paths so neither error can silently report a successful index drop.
1 parent ad3b10d commit eb603e4

2 files changed

Lines changed: 174 additions & 1 deletion

File tree

blockchain/indexers/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan s
619619
return subBucketClosure(dbTx, idxKey, nil)
620620
})
621621
if err != nil {
622-
return nil
622+
return err
623623
}
624624

625625
// Iterate through each sub-bucket in reverse, deepest-first, deleting
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) 2026 The btcsuite developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package indexers
6+
7+
import (
8+
"errors"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/btcsuite/btcd/database"
13+
_ "github.com/btcsuite/btcd/database/ffldb"
14+
"github.com/btcsuite/btcd/wire/v2"
15+
)
16+
17+
type bucketBase struct {
18+
database.Bucket
19+
}
20+
21+
// errorBucket wraps a database bucket and injects errors into operations used
22+
// while cataloging and deleting index buckets.
23+
type errorBucket struct {
24+
bucketBase
25+
forEachBucketErr error
26+
deleteBucketErr error
27+
}
28+
29+
// Bucket returns a wrapped nested bucket that preserves error injection.
30+
func (b *errorBucket) Bucket(key []byte) database.Bucket {
31+
bucket := b.bucketBase.Bucket.Bucket(key)
32+
if bucket == nil {
33+
return nil
34+
}
35+
36+
return &errorBucket{
37+
bucketBase: bucketBase{Bucket: bucket},
38+
forEachBucketErr: b.forEachBucketErr,
39+
deleteBucketErr: b.deleteBucketErr,
40+
}
41+
}
42+
43+
// ForEachBucket returns the configured cataloging error when present.
44+
func (b *errorBucket) ForEachBucket(fn func(k []byte) error) error {
45+
if b.forEachBucketErr != nil {
46+
return b.forEachBucketErr
47+
}
48+
49+
return b.bucketBase.Bucket.ForEachBucket(fn)
50+
}
51+
52+
// DeleteBucket returns the configured bucket deletion error when present.
53+
func (b *errorBucket) DeleteBucket(key []byte) error {
54+
if b.deleteBucketErr != nil {
55+
return b.deleteBucketErr
56+
}
57+
58+
return b.bucketBase.Bucket.DeleteBucket(key)
59+
}
60+
61+
// errorTx wraps the metadata bucket of a database transaction so errors can
62+
// be injected into bucket operations.
63+
type errorTx struct {
64+
database.Tx
65+
forEachBucketErr error
66+
deleteBucketErr error
67+
}
68+
69+
// Metadata returns the wrapped top-level metadata bucket.
70+
func (tx *errorTx) Metadata() database.Bucket {
71+
return &errorBucket{
72+
bucketBase: bucketBase{Bucket: tx.Tx.Metadata()},
73+
forEachBucketErr: tx.forEachBucketErr,
74+
deleteBucketErr: tx.deleteBucketErr,
75+
}
76+
}
77+
78+
// errorDB wraps all managed transactions with the configured error injector.
79+
type errorDB struct {
80+
database.DB
81+
forEachBucketErr error
82+
deleteBucketErr error
83+
}
84+
85+
// View invokes the view function with an error-injecting transaction.
86+
func (db *errorDB) View(fn func(database.Tx) error) error {
87+
return db.DB.View(func(tx database.Tx) error {
88+
return fn(&errorTx{
89+
Tx: tx,
90+
forEachBucketErr: db.forEachBucketErr,
91+
deleteBucketErr: db.deleteBucketErr,
92+
})
93+
})
94+
}
95+
96+
// Update invokes the update function with an error-injecting transaction.
97+
func (db *errorDB) Update(fn func(database.Tx) error) error {
98+
return db.DB.Update(func(tx database.Tx) error {
99+
return fn(&errorTx{
100+
Tx: tx,
101+
forEachBucketErr: db.forEachBucketErr,
102+
deleteBucketErr: db.deleteBucketErr,
103+
})
104+
})
105+
}
106+
107+
// createDropIndexTestDB creates a database with the metadata required to drop
108+
// a test index.
109+
func createDropIndexTestDB(t *testing.T, idxKey []byte) database.DB {
110+
t.Helper()
111+
112+
dbPath := filepath.Join(t.TempDir(), "blocks_ffldb")
113+
db, err := database.Create("ffldb", dbPath, wire.MainNet)
114+
if err != nil {
115+
t.Fatalf("unable to create test database: %v", err)
116+
}
117+
t.Cleanup(func() {
118+
if err := db.Close(); err != nil {
119+
t.Errorf("unable to close test database: %v", err)
120+
}
121+
})
122+
123+
err = db.Update(func(tx database.Tx) error {
124+
meta := tx.Metadata()
125+
indexesBucket, err := meta.CreateBucket(indexTipsBucketName)
126+
if err != nil {
127+
return err
128+
}
129+
if err := indexesBucket.Put(idxKey, []byte{0x01}); err != nil {
130+
return err
131+
}
132+
133+
_, err = meta.CreateBucket(idxKey)
134+
return err
135+
})
136+
if err != nil {
137+
t.Fatalf("unable to initialize test index: %v", err)
138+
}
139+
140+
return db
141+
}
142+
143+
// TestDropIndexCatalogError ensures errors encountered while cataloging index
144+
// buckets are returned to the caller.
145+
func TestDropIndexCatalogError(t *testing.T) {
146+
idxKey := []byte("testidx")
147+
testErr := errors.New("test catalog error")
148+
db := &errorDB{
149+
DB: createDropIndexTestDB(t, idxKey),
150+
forEachBucketErr: testErr,
151+
}
152+
153+
err := dropIndex(db, idxKey, "test index", nil)
154+
if !errors.Is(err, testErr) {
155+
t.Fatalf("unexpected error: got %v, want %v", err, testErr)
156+
}
157+
}
158+
159+
// TestDropIndexDeleteBucketError ensures errors encountered while deleting an
160+
// index bucket are returned to the caller.
161+
func TestDropIndexDeleteBucketError(t *testing.T) {
162+
idxKey := []byte("testidx")
163+
testErr := errors.New("test bucket deletion error")
164+
db := &errorDB{
165+
DB: createDropIndexTestDB(t, idxKey),
166+
deleteBucketErr: testErr,
167+
}
168+
169+
err := dropIndex(db, idxKey, "test index", nil)
170+
if !errors.Is(err, testErr) {
171+
t.Fatalf("unexpected error: got %v, want %v", err, testErr)
172+
}
173+
}

0 commit comments

Comments
 (0)