55 "fmt"
66 "log/slog"
77 "sort"
8+ "sync/atomic"
89 "time"
910
1011 "github.com/gagliardetto/solana-go"
@@ -27,30 +28,38 @@ type InboundKeyUpdate struct {
2728 Keys [][32 ]byte
2829}
2930
31+ // targetDiscoveryFullRefreshEvery controls how often a full GeolocationUser scan
32+ // is forced regardless of whether the GeoProbe target_update_count has changed.
33+ // At the default 60s interval, 5 means a full refresh every ~5 minutes.
34+ const targetDiscoveryFullRefreshEvery = 5
35+
3036// TargetDiscoveryConfig holds configuration for target discovery.
3137type TargetDiscoveryConfig struct {
32- GeoProbePubkey solana.PublicKey
33- Client GeolocationUserClient
34- CLITargets []ProbeAddress
35- CLIAllowedKeys [][32 ]byte
36- Interval time.Duration
37- Logger * slog.Logger
38+ GeoProbePubkey solana.PublicKey
39+ Client GeolocationUserClient
40+ CLITargets []ProbeAddress
41+ CLIAllowedKeys [][32 ]byte
42+ Interval time.Duration
43+ Logger * slog.Logger
44+ ProbeTargetUpdateCount * atomic.Uint32 // shared counter from parent discovery
3845}
3946
4047// TargetDiscovery polls GeolocationUser accounts and sends target/key updates
4148// when changes are detected. It filters for activated, paid users whose targets
4249// reference this probe's pubkey.
4350type TargetDiscovery struct {
44- log * slog.Logger
45- geoProbePubkey solana.PublicKey
46- client GeolocationUserClient
47- cliTargets []ProbeAddress
48- cliAllowedKeys [][32 ]byte
49- interval time.Duration
50-
51- cachedTargets []ProbeAddress
52- cachedInboundKeys [][32 ]byte
53- tickCount uint64
51+ log * slog.Logger
52+ geoProbePubkey solana.PublicKey
53+ client GeolocationUserClient
54+ cliTargets []ProbeAddress
55+ cliAllowedKeys [][32 ]byte
56+ interval time.Duration
57+ probeTargetUpdateCount * atomic.Uint32
58+
59+ cachedTargets []ProbeAddress
60+ cachedInboundKeys [][32 ]byte
61+ tickCount uint64
62+ lastSeenTargetUpdateCount uint32
5463}
5564
5665// NewTargetDiscovery creates a new TargetDiscovery instance.
@@ -69,12 +78,13 @@ func NewTargetDiscovery(cfg *TargetDiscoveryConfig) (*TargetDiscovery, error) {
6978 }
7079
7180 return & TargetDiscovery {
72- log : cfg .Logger ,
73- geoProbePubkey : cfg .GeoProbePubkey ,
74- client : cfg .Client ,
75- cliTargets : cfg .CLITargets ,
76- cliAllowedKeys : cfg .CLIAllowedKeys ,
77- interval : cfg .Interval ,
81+ log : cfg .Logger ,
82+ geoProbePubkey : cfg .GeoProbePubkey ,
83+ client : cfg .Client ,
84+ cliTargets : cfg .CLITargets ,
85+ cliAllowedKeys : cfg .CLIAllowedKeys ,
86+ interval : cfg .Interval ,
87+ probeTargetUpdateCount : cfg .ProbeTargetUpdateCount ,
7888 }, nil
7989}
8090
@@ -111,6 +121,11 @@ func (d *TargetDiscovery) discoverAndSend(ctx context.Context, targetCh chan<- T
111121 return
112122 }
113123
124+ // nil targets means the scan was skipped (target_update_count unchanged).
125+ if targets == nil && inboundKeys == nil {
126+ return
127+ }
128+
114129 if ! probeAddressSlicesEqual (targets , d .cachedTargets ) {
115130 d .cachedTargets = targets
116131 select {
@@ -131,10 +146,21 @@ func (d *TargetDiscovery) discoverAndSend(ctx context.Context, targetCh chan<- T
131146}
132147
133148// discover performs a single discovery cycle: fetch users, filter, extract targets/keys,
134- // merge with CLI values.
149+ // merge with CLI values. Returns nil, nil, nil when the scan is skipped.
135150func (d * TargetDiscovery ) discover (ctx context.Context ) ([]ProbeAddress , [][32 ]byte , error ) {
151+ forceFullRefresh := d .tickCount % targetDiscoveryFullRefreshEvery == 0
136152 d .tickCount ++
137153
154+ if d .probeTargetUpdateCount != nil && ! forceFullRefresh {
155+ current := d .probeTargetUpdateCount .Load ()
156+ if current == d .lastSeenTargetUpdateCount && d .tickCount > 1 {
157+ d .log .Debug ("GeoProbe target_update_count unchanged, skipping target scan" ,
158+ "targetUpdateCount" , current )
159+ return nil , nil , nil
160+ }
161+ d .lastSeenTargetUpdateCount = current
162+ }
163+
138164 users , err := d .client .GetGeolocationUsers (ctx )
139165 if err != nil {
140166 return nil , nil , fmt .Errorf ("failed to fetch GeolocationUser accounts: %w" , err )
@@ -195,6 +221,11 @@ func (d *TargetDiscovery) discover(ctx context.Context) ([]ProbeAddress, [][32]b
195221 mergedTargets := mergeProbes (d .cliTargets , onchainTargets )
196222 mergedKeys := mergeKeys (d .cliAllowedKeys , onchainKeys )
197223
224+ // Sync lastSeenTargetUpdateCount after a full scan (covers forced refresh path).
225+ if d .probeTargetUpdateCount != nil {
226+ d .lastSeenTargetUpdateCount = d .probeTargetUpdateCount .Load ()
227+ }
228+
198229 d .log .Debug ("Target discovery tick" ,
199230 "users" , len (users ),
200231 "onchainOutbound" , len (onchainTargets ),
0 commit comments