@@ -11,6 +11,7 @@ import (
1111 "sync"
1212 "time"
1313
14+ "github.com/ethereum/go-ethereum/common/hexutil"
1415 "github.com/gobitfly/beaconchain/pkg/commons/cache"
1516 "github.com/gobitfly/beaconchain/pkg/commons/config"
1617 "github.com/gobitfly/beaconchain/pkg/commons/log"
@@ -29,6 +30,7 @@ import (
2930 "golang.org/x/exp/maps"
3031 "golang.org/x/sync/errgroup"
3132
33+ "github.com/gobitfly/beaconchain/pkg/commons/metrics"
3234 edb "github.com/gobitfly/beaconchain/pkg/exporter/db"
3335)
3436
@@ -684,6 +686,109 @@ func ExportSlot(client rpc.Client, slot uint64, isHeadEpoch bool, tx *sqlx.Tx) e
684686 return nil
685687 })
686688 }
689+
690+ // update pubkey => validator lookup index in clickhouse
691+ g .Go (func () error {
692+ metrics .Tasks .WithLabelValues ("slot_exporter_upkeep_lookup_public_key" ).Inc ()
693+ startTotal := time .Now ()
694+ defer metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_public_key.total" ).Observe (time .Since (startTotal ).Seconds ())
695+ tbl , err := edb .NewLookupExternalTable ()
696+ if err != nil {
697+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_public_key" ).Inc ()
698+ return fmt .Errorf ("error creating external table: %w" , err )
699+ }
700+ startAppend := time .Now ()
701+ var rowsAppended int64
702+ for _ , v := range block .Validators {
703+ if err := tbl .Append (string (edb .PublicKeySelector ), hexutil .Encode (v .PublicKey ), uint64 (v .Index )); err != nil {
704+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_public_key" ).Inc ()
705+ return fmt .Errorf ("error appending row to external table: %w" , err )
706+ }
707+ rowsAppended ++
708+ }
709+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_public_key.append_rows" ).Observe (time .Since (startAppend ).Seconds ())
710+ metrics .Counter .WithLabelValues ("slot_exporter_upkeep_lookup_public_key.rows_appended" ).Add (float64 (rowsAppended ))
711+ ctx , cancel := context .WithTimeout (context .Background (), 1 * time .Minute )
712+ defer cancel ()
713+ startCH := time .Now ()
714+ if err := edb .UpdateLookupTable (ctx , edb .PublicKeySelector , tbl ); err != nil {
715+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_public_key" ).Inc ()
716+ return fmt .Errorf ("error updating external table: %w" , err )
717+ }
718+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_public_key.ch_update" ).Observe (time .Since (startCH ).Seconds ())
719+ return nil
720+ })
721+
722+ // update withdrawal credentials => validator index lookup in clickhouse (in parallel)
723+ g .Go (func () error {
724+ metrics .Tasks .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials" ).Inc ()
725+ startTotal := time .Now ()
726+ defer metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials.total" ).Observe (time .Since (startTotal ).Seconds ())
727+ tbl , err := edb .NewLookupExternalTable ()
728+ if err != nil {
729+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials" ).Inc ()
730+ return fmt .Errorf ("error creating external table: %w" , err )
731+ }
732+ startAppend := time .Now ()
733+ var rowsAppended int64
734+ for _ , v := range block .Validators {
735+ if len (v .WithdrawalCredentials ) == 0 {
736+ continue
737+ }
738+ selector := hexutil .Encode (v .WithdrawalCredentials )
739+ if err := tbl .Append (string (edb .WithdrawalCredentialsSelector ), selector , uint64 (v .Index )); err != nil {
740+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials" ).Inc ()
741+ return fmt .Errorf ("error appending row to external table: %w" , err )
742+ }
743+ rowsAppended ++
744+ }
745+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials.append_rows" ).Observe (time .Since (startAppend ).Seconds ())
746+ metrics .Counter .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials.rows_appended" ).Add (float64 (rowsAppended ))
747+ ctx , cancel := context .WithTimeout (context .Background (), 1 * time .Minute )
748+ defer cancel ()
749+ startCH := time .Now ()
750+ if err := edb .UpdateLookupTable (ctx , edb .WithdrawalCredentialsSelector , tbl ); err != nil {
751+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials" ).Inc ()
752+ return fmt .Errorf ("error updating external table: %w" , err )
753+ }
754+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_credentials.ch_update" ).Observe (time .Since (startCH ).Seconds ())
755+ return nil
756+ })
757+
758+ // update withdrawal address => validator index lookup in clickhouse (in parallel)
759+ g .Go (func () error {
760+ metrics .Tasks .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address" ).Inc ()
761+ startTotal := time .Now ()
762+ defer metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address.total" ).Observe (time .Since (startTotal ).Seconds ())
763+ tbl , err := edb .NewLookupExternalTable ()
764+ if err != nil {
765+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address" ).Inc ()
766+ return fmt .Errorf ("error creating external table: %w" , err )
767+ }
768+ startAppend := time .Now ()
769+ var rowsAppended int64
770+ for _ , v := range block .Validators {
771+ if addr , ok := eth1AddrFromWithdrawalCreds (v .WithdrawalCredentials ); ok {
772+ selector := hexutil .Encode (addr )
773+ if err := tbl .Append (string (edb .WithdrawalAddressSelector ), selector , uint64 (v .Index )); err != nil {
774+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address" ).Inc ()
775+ return fmt .Errorf ("error appending row to external table: %w" , err )
776+ }
777+ rowsAppended ++
778+ }
779+ }
780+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address.append_rows" ).Observe (time .Since (startAppend ).Seconds ())
781+ metrics .Counter .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address.rows_appended" ).Add (float64 (rowsAppended ))
782+ ctx , cancel := context .WithTimeout (context .Background (), 1 * time .Minute )
783+ defer cancel ()
784+ startCH := time .Now ()
785+ if err := edb .UpdateLookupTable (ctx , edb .WithdrawalAddressSelector , tbl ); err != nil {
786+ metrics .Errors .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address" ).Inc ()
787+ return fmt .Errorf ("error updating external table: %w" , err )
788+ }
789+ metrics .TaskDuration .WithLabelValues ("slot_exporter_upkeep_lookup_withdrawal_address.ch_update" ).Observe (time .Since (startCH ).Seconds ())
790+ return nil
791+ })
687792 }
688793 var epochParticipationStats * types.ValidatorParticipation
689794 if epoch > 0 {
@@ -758,3 +863,18 @@ func (d *slotExporterData) OnChainReorg(event *constypes.StandardEventChainReorg
758863func (d * slotExporterData ) OnFinalizedCheckpoint (event * constypes.StandardFinalizedCheckpointResponse ) (err error ) {
759864 return nil // nop
760865}
866+
867+ // eth1AddrFromWithdrawalCreds extracts the 20-byte ETH1 address from 32-byte
868+ // withdrawal credentials that encode an address. Supports both 0x01 and 0x02
869+ // prefixes (wc[0] == 0x01 or 0x02). Returns (addr, true) on success.
870+ func eth1AddrFromWithdrawalCreds (wc []byte ) ([]byte , bool ) {
871+ if len (wc ) != 32 {
872+ return nil , false
873+ }
874+ if wc [0 ] == 0x01 || wc [0 ] == 0x02 {
875+ addr := make ([]byte , 20 )
876+ copy (addr , wc [12 :])
877+ return addr , true
878+ }
879+ return nil , false
880+ }
0 commit comments