99 "fmt"
1010 "math/big"
1111 "strings"
12- "sync"
1312 "sync/atomic"
1413
1514 cmtservice "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
@@ -41,13 +40,6 @@ type Client struct {
4140 authClients []authtypes.QueryClient // Auth query clients
4241 conns []* grpc.ClientConn // Owned gRPC connections (for cleanup)
4342 rr uint32 // Round-robin counter for endpoint selection
44-
45- // Pagination cursor carried between pending-outbound polls. The page budget
46- // bounds one poll's work; this makes the budget cost latency rather than
47- // coverage, so a set larger than the budget is still walked in full over
48- // successive ticks. Nil means start from the beginning.
49- pendingMu sync.Mutex
50- pendingCursor []byte
5143}
5244
5345// New creates a new Client by dialing the provided gRPC URLs.
@@ -405,8 +397,14 @@ func (c *Client) GetPendingFundMigrations(ctx context.Context) ([]*utsstypes.Fun
405397// Page size and page cap for the pending-outbound walk. The cap bounds a single
406398// poll; the remainder is picked up on the next tick.
407399const (
408- pendingOutboundPageSize = 1000
409- pendingOutboundMaxPages = 20
400+ // AllPendingOutbounds pages by offset and returns only Total, never a NextKey,
401+ // so a key-based walk stops after one page. It also loads and sorts the whole
402+ // collection per call, so paging saves the server nothing. The sort is not
403+ // stable and orders on CreatedAt, a block height, so rows sharing a height can
404+ // change relative order between calls — which makes offset paging able to skip
405+ // a row outright. One generous request plus a Total check is the only shape
406+ // that is both correct and cheap against that server.
407+ pendingOutboundLimit = 100_000
410408
411409 chainConfigPageSize = 200
412410 chainConfigMaxPages = 20
@@ -415,68 +413,39 @@ const (
415413// GetAllPendingOutbounds retrieves pending outbound transactions from Push Chain,
416414// sorted by created_at (block height) ascending — oldest first.
417415//
418- // The result is paged rather than a single query. An outbound only leaves the
419- // pending set once a quorum vote terminalizes it, so any row that cannot reach
420- // one — for example a destination execution whose observation was never seen —
421- // stays at the head of an oldest-first list forever. Reading one fixed page
422- // would let such a prefix hide every newer outbound from signing, on every
423- // chain, since this query is not chain-scoped.
416+ // An outbound only leaves the pending set once a quorum vote terminalizes it, so
417+ // a row that cannot reach one stays at the head of an oldest-first list forever.
418+ // The request must therefore cover the whole set: a fixed small page would let
419+ // such a prefix hide every newer outbound from signing, on every chain, since
420+ // this query is not chain-scoped.
424421func (c * Client ) GetAllPendingOutbounds (ctx context.Context ) ([]* uexecutortypes.PendingOutboundEntry , []* uexecutortypes.OutboundTx , error ) {
425- c .pendingMu .Lock ()
426- defer c .pendingMu .Unlock ()
427-
428- var (
429- entries []* uexecutortypes.PendingOutboundEntry
430- outbounds []* uexecutortypes.OutboundTx
431- nextKey = c .pendingCursor
422+ resp , err := retryWithRoundRobin (
423+ len (c .uexecutorClients ),
424+ & c .rr ,
425+ func (idx int ) (* uexecutortypes.QueryAllPendingOutboundsResponse , error ) {
426+ return c .uexecutorClients [idx ].AllPendingOutbounds (ctx , & uexecutortypes.QueryAllPendingOutboundsRequest {
427+ Pagination : & query.PageRequest {Limit : pendingOutboundLimit },
428+ })
429+ },
430+ "GetAllPendingOutbounds" ,
431+ c .logger ,
432432 )
433+ if err != nil {
434+ return nil , nil , err
435+ }
433436
434- for page := 0 ; page < pendingOutboundMaxPages ; page ++ {
435- key := nextKey
436- resp , err := retryWithRoundRobin (
437- len (c .uexecutorClients ),
438- & c .rr ,
439- func (idx int ) (* uexecutortypes.QueryAllPendingOutboundsResponse , error ) {
440- return c .uexecutorClients [idx ].AllPendingOutbounds (ctx , & uexecutortypes.QueryAllPendingOutboundsRequest {
441- Pagination : & query.PageRequest {Key : key , Limit : pendingOutboundPageSize },
442- })
443- },
444- "GetAllPendingOutbounds" ,
445- c .logger ,
446- )
447- if err != nil {
448- // Return what we have rather than nothing: a later page failing must
449- // not stop the caller acting on the pages that did arrive. Resume from
450- // the failed page next time instead of losing the ground already made.
451- if len (entries ) > 0 {
452- c .pendingCursor = key
453- c .logger .Warn ().Err (err ).Int ("page" , page ).Msg ("pending outbound page failed, using pages fetched so far" )
454- return entries , outbounds , nil
455- }
456- c .pendingCursor = nil
457- return nil , nil , err
458- }
459-
460- entries = append (entries , resp .Entries ... )
461- outbounds = append (outbounds , resp .Outbounds ... )
462-
463- if resp .Pagination == nil || len (resp .Pagination .NextKey ) == 0 {
464- // Reached the end; the next poll starts from the beginning again so
465- // rows added at the tail since the walk began are picked up.
466- c .pendingCursor = nil
467- return entries , outbounds , nil
468- }
469- nextKey = resp .Pagination .NextKey
437+ // Total is authoritative for the size of the pending set, so a shortfall means
438+ // rows we will not act on this poll. Loud rather than silent: those outbounds
439+ // are invisible to signing until the set shrinks.
440+ if resp .Pagination != nil && resp .Pagination .Total > uint64 (len (resp .Entries )) {
441+ c .logger .Error ().
442+ Uint64 ("total" , resp .Pagination .Total ).
443+ Int ("received" , len (resp .Entries )).
444+ Uint64 ("limit" , pendingOutboundLimit ).
445+ Msg ("pending outbound set exceeds the request limit; the remainder is not being signed" )
470446 }
471447
472- // Budget spent mid-set. Park the cursor so the next tick continues from here
473- // rather than re-reading the same prefix forever.
474- c .pendingCursor = nextKey
475- c .logger .Warn ().
476- Int ("max_pages" , pendingOutboundMaxPages ).
477- Int ("fetched" , len (entries )).
478- Msg ("pending outbound page budget spent; continuing from this cursor next poll" )
479- return entries , outbounds , nil
448+ return resp .Entries , resp .Outbounds , nil
480449}
481450
482451// createGRPCConnection creates a gRPC connection with appropriate transport security.
0 commit comments