@@ -5,11 +5,13 @@ import (
55 "crypto/tls"
66 "fmt"
77 "math/big"
8+ "slices"
89 "strconv"
910 "strings"
1011 "sync"
1112
1213 "github.com/rs/zerolog/log"
14+ "golang.org/x/sync/errgroup"
1315
1416 "github.com/ClickHouse/clickhouse-go/v2"
1517 config "github.com/thirdweb-dev/indexer/configs"
@@ -250,6 +252,153 @@ func GetBlockDataFromClickHouseV2(chainId uint64, startBlockNumber uint64, endBl
250252 return blockData , nil
251253}
252254
255+ func joinUint64sForIN (nums []uint64 ) string {
256+ var b strings.Builder
257+ b .Grow (len (nums ) * 12 )
258+ for i , n := range nums {
259+ if i > 0 {
260+ b .WriteByte (',' )
261+ }
262+ b .WriteString (strconv .FormatUint (n , 10 ))
263+ }
264+ return b .String ()
265+ }
266+
267+ func queryBlocksByBlockNumbers (chainId uint64 , nums []uint64 ) ([]common.Block , error ) {
268+ if len (nums ) == 0 {
269+ return nil , nil
270+ }
271+ q := fmt .Sprintf (
272+ "SELECT %s FROM %s.blocks FINAL WHERE chain_id = %d AND block_number IN (%s) ORDER BY block_number" ,
273+ strings .Join (defaultBlockFields , ", " ),
274+ config .Cfg .CommitterClickhouseDatabase ,
275+ chainId ,
276+ joinUint64sForIN (nums ),
277+ )
278+ return execQueryV2 [common.Block ](q )
279+ }
280+
281+ func queryTransactionsByBlockNumbers (chainId uint64 , nums []uint64 ) ([]common.Transaction , error ) {
282+ if len (nums ) == 0 {
283+ return nil , nil
284+ }
285+ q := fmt .Sprintf (
286+ "SELECT %s FROM %s.transactions FINAL WHERE chain_id = %d AND block_number IN (%s) ORDER BY block_number, transaction_index" ,
287+ strings .Join (defaultTransactionFields , ", " ),
288+ config .Cfg .CommitterClickhouseDatabase ,
289+ chainId ,
290+ joinUint64sForIN (nums ),
291+ )
292+ return execQueryV2 [common.Transaction ](q )
293+ }
294+
295+ func queryLogsByBlockNumbers (chainId uint64 , nums []uint64 ) ([]common.Log , error ) {
296+ if len (nums ) == 0 {
297+ return nil , nil
298+ }
299+ q := fmt .Sprintf (
300+ "SELECT %s FROM %s.logs FINAL WHERE chain_id = %d AND block_number IN (%s) ORDER BY block_number, log_index" ,
301+ strings .Join (defaultLogFields , ", " ),
302+ config .Cfg .CommitterClickhouseDatabase ,
303+ chainId ,
304+ joinUint64sForIN (nums ),
305+ )
306+ return execQueryV2 [common.Log ](q )
307+ }
308+
309+ func queryTracesByBlockNumbers (chainId uint64 , nums []uint64 ) ([]common.Trace , error ) {
310+ if len (nums ) == 0 {
311+ return nil , nil
312+ }
313+ q := fmt .Sprintf (
314+ "SELECT %s FROM %s.traces FINAL WHERE chain_id = %d AND block_number IN (%s) ORDER BY block_number, transaction_index" ,
315+ strings .Join (defaultTraceFields , ", " ),
316+ config .Cfg .CommitterClickhouseDatabase ,
317+ chainId ,
318+ joinUint64sForIN (nums ),
319+ )
320+ return execQueryV2 [common.Trace ](q )
321+ }
322+
323+ // GetBlockDataFromClickHouseForBlockNumbers loads stored block data for specific block numbers using
324+ // block_number IN (...). Callers that send very large lists should chunk requests (e.g. reorg-api batches by REORG_API_CLICKHOUSE_BATCH_SIZE).
325+ func GetBlockDataFromClickHouseForBlockNumbers (chainId uint64 , blockNumbers []uint64 ) ([]* common.BlockData , error ) {
326+ if len (blockNumbers ) == 0 {
327+ return nil , nil
328+ }
329+ nums := slices .Clone (blockNumbers )
330+ slices .Sort (nums )
331+ nums = slices .Compact (nums )
332+
333+ var blocks []common.Block
334+ var txs []common.Transaction
335+ var logs []common.Log
336+ var traces []common.Trace
337+ g := new (errgroup.Group )
338+ g .Go (func () (err error ) {
339+ blocks , err = queryBlocksByBlockNumbers (chainId , nums )
340+ return err
341+ })
342+ g .Go (func () (err error ) {
343+ txs , err = queryTransactionsByBlockNumbers (chainId , nums )
344+ return err
345+ })
346+ g .Go (func () (err error ) {
347+ logs , err = queryLogsByBlockNumbers (chainId , nums )
348+ return err
349+ })
350+ g .Go (func () (err error ) {
351+ traces , err = queryTracesByBlockNumbers (chainId , nums )
352+ return err
353+ })
354+ if err := g .Wait (); err != nil {
355+ return nil , err
356+ }
357+
358+ blocksByNum := make (map [uint64 ]common.Block , len (blocks ))
359+ for _ , b := range blocks {
360+ if b .Number != nil {
361+ blocksByNum [b .Number .Uint64 ()] = b
362+ }
363+ }
364+ txByNum := make (map [uint64 ][]common.Transaction )
365+ for _ , t := range txs {
366+ if t .BlockNumber != nil {
367+ bn := t .BlockNumber .Uint64 ()
368+ txByNum [bn ] = append (txByNum [bn ], t )
369+ }
370+ }
371+ logsByNum := make (map [uint64 ][]common.Log )
372+ for _ , l := range logs {
373+ if l .BlockNumber != nil {
374+ bn := l .BlockNumber .Uint64 ()
375+ logsByNum [bn ] = append (logsByNum [bn ], l )
376+ }
377+ }
378+ tracesByNum := make (map [uint64 ][]common.Trace )
379+ for _ , tr := range traces {
380+ if tr .BlockNumber != nil {
381+ bn := tr .BlockNumber .Uint64 ()
382+ tracesByNum [bn ] = append (tracesByNum [bn ], tr )
383+ }
384+ }
385+
386+ out := make ([]* common.BlockData , 0 , len (nums ))
387+ for _ , bn := range nums {
388+ b , ok := blocksByNum [bn ]
389+ if ! ok || b .ChainId == nil || b .Number == nil || b .ChainId .Uint64 () == 0 {
390+ continue
391+ }
392+ out = append (out , & common.BlockData {
393+ Block : b ,
394+ Transactions : txByNum [bn ],
395+ Logs : logsByNum [bn ],
396+ Traces : tracesByNum [bn ],
397+ })
398+ }
399+ return out , nil
400+ }
401+
253402// GetTransactionMismatchRangeFromClickHouseV2 checks, for blocks in the given range,
254403// where the stored transaction_count in the blocks table does not match the number
255404// of transactions in the transactions table. It returns the minimum and maximum
0 commit comments