@@ -11,8 +11,10 @@ import (
1111 "testing"
1212 "time"
1313
14+ "github.com/btcsuite/btcd/chaincfg/chainhash"
1415 "github.com/btcsuite/btcwallet/walletdb"
1516 _ "github.com/btcsuite/btcwallet/walletdb/bdb"
17+ "github.com/stretchr/testify/require"
1618)
1719
1820func createTestIndex (t testing.TB ) (func (), * headerIndex , error ) {
@@ -43,6 +45,16 @@ func createTestIndex(t testing.TB) (func(), *headerIndex, error) {
4345 return cleanUp , filterDB , nil
4446}
4547
48+ // TestAddHeadersIndexRetrieve tests the header index functionality by verifying
49+ // the writing of random headers, ensuring the database tip matches the last
50+ // inserted header, checking each header can be retrieved by hash, and testing
51+ // index truncation.
52+ // It specifically exercises the truncateIndices method with an explicit list of
53+ // headers to remove, ensuring proper index maintenance when headers are removed
54+ // from the chain. The test first writes a batch of headers, verifies the chain
55+ // tip, confirms retrieval by hash for all entries, truncates the last header,
56+ // and finally verifies the tip has been properly updated to the second-to-last
57+ // entry.
4658func TestAddHeadersIndexRetrieve (t * testing.T ) {
4759 cleanUp , hIndex , err := createTestIndex (t )
4860 defer cleanUp ()
@@ -90,8 +102,12 @@ func TestAddHeadersIndexRetrieve(t *testing.T) {
90102 // Next if we truncate the index by one, then we should end up at the
91103 // second to last entry for the tip.
92104 newTip := headerIndex [numHeaders - 2 ]
93- if err := hIndex .truncateIndex (& newTip .hash , true ); err != nil {
94- t .Fatalf ("unable to truncate index: %v" , err )
105+
106+ // Truncate just the last header.
107+ headersToTruncate := []* chainhash.Hash {& lastEntry .hash }
108+ err = hIndex .truncateIndices (& newTip .hash , headersToTruncate , true )
109+ if err != nil {
110+ t .Fatalf ("unable to truncate indices: %v" , err )
95111 }
96112
97113 // This time the database tip should be the _second_ to last entry
@@ -113,7 +129,11 @@ func TestAddHeadersIndexRetrieve(t *testing.T) {
113129
114130// TestHeaderStorageFallback makes sure that the changes to the header storage
115131// location in the bbolt database for reduced memory consumption don't impact
116- // existing users that already have entries in their database.
132+ // existing users that already have entries in their database. The test verifies
133+ // compatibility with both old format headers stored directly in the root bucket
134+ // and new format headers (stored in sub-buckets). It tests reading from both
135+ // formats and ensures that the truncation functionality correctly handles
136+ // removing headers from either storage format.
117137func TestHeaderStorageFallback (t * testing.T ) {
118138 cleanUp , hIndex , err := createTestIndex (t )
119139 if err != nil {
@@ -184,49 +204,95 @@ func TestHeaderStorageFallback(t *testing.T) {
184204 }
185205 }
186206
187- // And finally, we trim the chain all the way down to the first header.
188- // To do so, we first need to make sure the tip points to the last entry
189- // we added.
190- lastEntry := newHeaderEntries [len (newHeaderEntries )- 1 ]
191- if err := hIndex .truncateIndex (& lastEntry .hash , false ); err != nil {
192- t .Fatalf ("error setting new tip: %v" , err )
193- }
194- for _ , header := range newHeaderEntries {
195- if err := hIndex .truncateIndex (& header .hash , true ); err != nil {
196- t .Fatalf ("error truncating tip: %v" , err )
197- }
198- }
199- for _ , header := range oldHeaderEntries {
200- if err := hIndex .truncateIndex (& header .hash , true ); err != nil {
201- t .Fatalf ("error truncating tip: %v" , err )
202- }
207+ // Now we'll test the truncation functionality by truncating all the way
208+ // back to the first old header. We'll do this in steps to verify the
209+ // truncation works properly on both new and old format headers.
210+
211+ // First, set the chain tip to the last new header without removing
212+ // anything.
213+ lastNewHeader := newHeaderEntries [len (newHeaderEntries )- 1 ]
214+ err = hIndex .truncateIndices (& lastNewHeader .hash , nil , false )
215+ require .NoError (t , err )
216+
217+ // Next, truncate all new headers except the first one.
218+ truncationPoint := newHeaderEntries [0 ]
219+ headersToTruncate := make ([]* chainhash.Hash , 0 , len (newHeaderEntries )- 1 )
220+ for i := 1 ; i < len (newHeaderEntries ); i ++ {
221+ headersToTruncate = append (
222+ headersToTruncate , & newHeaderEntries [i ].hash ,
223+ )
203224 }
225+ err = hIndex .truncateIndices (
226+ & truncationPoint .hash , headersToTruncate , true ,
227+ )
228+ require .NoError (t , err )
204229
205- // All the headers except the very last should now be deleted.
206- for i := 0 ; i < len (oldHeaderEntries )- 1 ; i ++ {
207- header := oldHeaderEntries [i ]
208- if _ , err := hIndex .heightFromHash (& header .hash ); err == nil {
209- t .Fatalf ("expected error reading old entry %x" ,
210- header .hash [:])
230+ // Verify that only the first new header remains and all others
231+ // are gone.
232+ for i , header := range newHeaderEntries {
233+ height , err := hIndex .heightFromHash (& header .hash )
234+ if i == 0 {
235+ // First header should still be there.
236+ msg := "first new header should still exist"
237+ require .NoError (t , err , msg )
238+ require .Equal (t , header .height , height )
239+ continue
211240 }
212- }
213- for _ , header := range newHeaderEntries {
214- if _ , err := hIndex .heightFromHash (& header .hash ); err == nil {
215- t .Fatalf ("expected error reading old entry %x" ,
216- header .hash [:])
241+
242+ if err == nil {
243+ // All other headers should be gone.
244+ msg := fmt .Sprintf ("header at index %d should be " +
245+ "deleted, but still exists" , i )
246+ require .Fail (t , msg )
217247 }
218248 }
219249
220- // The last entry should still be there.
221- lastEntry = oldHeaderEntries [len (oldHeaderEntries )- 1 ]
222- height , err := hIndex .heightFromHash (& lastEntry .hash )
223- if err != nil {
224- t .Fatalf ("error reading old entry: %v" , err )
225- }
250+ // Now truncate back to the last old header.
251+ truncationPoint = oldHeaderEntries [len (oldHeaderEntries )- 1 ]
252+ headersToTruncate = []* chainhash.Hash {& newHeaderEntries [0 ].hash }
253+ err = hIndex .truncateIndices (
254+ & truncationPoint .hash , headersToTruncate , true ,
255+ )
256+ require .NoError (t , err , "error truncating to old headers" )
257+
258+ // Verify all new headers are gone.
259+ for i , header := range newHeaderEntries {
260+ _ , err := hIndex .heightFromHash (& header .hash )
261+ msg := fmt .Sprintf ("new header at index %d should be deleted, " +
262+ "but still exists" , i )
263+ require .Error (t , err , msg )
264+ }
265+
266+ // Finally, truncate to the first old header.
267+ truncationPoint = oldHeaderEntries [0 ]
268+ headersToTruncate = make ([]* chainhash.Hash , 0 , len (oldHeaderEntries )- 1 )
269+ for i := 1 ; i < len (oldHeaderEntries ); i ++ {
270+ headersToTruncate = append (
271+ headersToTruncate , & oldHeaderEntries [i ].hash ,
272+ )
273+ }
274+ err = hIndex .truncateIndices (
275+ & truncationPoint .hash , headersToTruncate , true ,
276+ )
277+ require .NoError (t , err , "error truncating to old headers" )
226278
227- if height != lastEntry .height {
228- t .Fatalf ("unexpected height, got %d wanted %d" , height ,
229- lastEntry .height )
279+ // Verify only the first old header remains.
280+ for i , header := range oldHeaderEntries {
281+ height , err := hIndex .heightFromHash (& header .hash )
282+ if i == 0 {
283+ // First header should still be there.
284+ msg := "first old header should still exist"
285+ require .NoError (t , err , msg )
286+ require .Equal (t , header .height , height )
287+ continue
288+ }
289+
290+ if err == nil {
291+ // All other headers should be gone.
292+ msg := fmt .Sprintf ("old header at index %d should be " +
293+ "deleted, but still exists" , i )
294+ require .Fail (t , msg )
295+ }
230296 }
231297}
232298
0 commit comments