@@ -82,10 +82,10 @@ func TestConcurrentReadsAndPrune(t *testing.T) {
8282 }
8383
8484 store , err := NewStore (StoreConfig {
85- DBDirectory : dir ,
86- MaxBlocksPerFile : 500 ,
87- KeepRecent : 600 ,
88- DisableTxIndexLookup : true ,
85+ DBDirectory : dir ,
86+ MaxBlocksPerFile : 500 ,
87+ KeepRecent : 600 ,
88+ TxIndexBackend : "none" ,
8989 })
9090 require .NoError (t , err )
9191 t .Cleanup (func () { _ = store .Close () })
@@ -126,6 +126,56 @@ func TestConcurrentReadsAndPrune(t *testing.T) {
126126 require .NoError (t , g .Wait ())
127127}
128128
129+ // TestConcurrentTargetedReadsAndPrune exercises GetReceiptByTxHashInBlock
130+ // (single-file candidate path) concurrently with pruning. Without holding
131+ // pruneMu across fileForBlock and the targeted query, a prune could delete
132+ // the candidate file between those steps and DuckDB would error.
133+ func TestConcurrentTargetedReadsAndPrune (t * testing.T ) {
134+ dir := t .TempDir ()
135+
136+ for _ , start := range []uint64 {0 , 500 , 1000 } {
137+ require .NoError (t , createTestReceiptFile (dir , start , 500 ))
138+ }
139+
140+ store , err := NewStore (StoreConfig {
141+ DBDirectory : dir ,
142+ MaxBlocksPerFile : 500 ,
143+ KeepRecent : 600 ,
144+ TxIndexBackend : "none" ,
145+ })
146+ require .NoError (t , err )
147+ t .Cleanup (func () { _ = store .Close () })
148+
149+ ctx := context .Background ()
150+ txHash := common .BigToHash (new (big.Int ).SetUint64 (250 ))
151+
152+ result , err := store .GetReceiptByTxHashInBlock (ctx , txHash , 250 )
153+ require .NoError (t , err )
154+ require .NotNil (t , result )
155+
156+ const numReaders = 32
157+ const readsPerReader = 100
158+
159+ g , _ := errgroup .WithContext (ctx )
160+ for i := 0 ; i < numReaders ; i ++ {
161+ g .Go (func () error {
162+ for j := 0 ; j < readsPerReader ; j ++ {
163+ if _ , err := store .GetReceiptByTxHashInBlock (ctx , txHash , 250 ); err != nil {
164+ return fmt .Errorf ("GetReceiptByTxHashInBlock(250): %w" , err )
165+ }
166+ }
167+ return nil
168+ })
169+ }
170+
171+ g .Go (func () error {
172+ store .PruneOldFiles (600 )
173+ return nil
174+ })
175+
176+ require .NoError (t , g .Wait ())
177+ }
178+
129179// TestOnFileRotationNotBlockedByPruneMu verifies the structural property
130180// that OnFileRotation only acquires mu (the file-list lock), never pruneMu
131181// (the file-lifetime lock). We hold pruneMu.RLock to simulate in-flight
@@ -135,9 +185,9 @@ func TestOnFileRotationNotBlockedByPruneMu(t *testing.T) {
135185 require .NoError (t , createTestReceiptFile (dir , 0 , 1 ))
136186
137187 store , err := NewStore (StoreConfig {
138- DBDirectory : dir ,
139- MaxBlocksPerFile : 500 ,
140- DisableTxIndexLookup : true ,
188+ DBDirectory : dir ,
189+ MaxBlocksPerFile : 500 ,
190+ TxIndexBackend : "none" ,
141191 })
142192 require .NoError (t , err )
143193 t .Cleanup (func () { _ = store .Close () })
@@ -170,10 +220,10 @@ func TestConcurrentReadsPruneAndRotation(t *testing.T) {
170220 }
171221
172222 store , err := NewStore (StoreConfig {
173- DBDirectory : dir ,
174- MaxBlocksPerFile : 500 ,
175- KeepRecent : 1000 ,
176- DisableTxIndexLookup : true ,
223+ DBDirectory : dir ,
224+ MaxBlocksPerFile : 500 ,
225+ KeepRecent : 1000 ,
226+ TxIndexBackend : "none" ,
177227 })
178228 require .NoError (t , err )
179229 t .Cleanup (func () { _ = store .Close () })
0 commit comments