LCOV - code coverage report
Current view: top level - disco/events - fd_event_client.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 789 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 104 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_event_client.h"
       3             : 
       4             : #include "../../waltz/resolv/fd_netdb.h"
       5             : #include "../../waltz/http/fd_url.h"
       6             : #include "../../waltz/grpc/fd_grpc_client.h"
       7             : #include "../../waltz/grpc/fd_grpc_client_private.h"
       8             : #include "../../ballet/pb/fd_pb_tokenize.h"
       9             : #include "../../ballet/pb/fd_pb_encode.h"
      10             : #include "../../ballet/hex/fd_hex.h"
      11             : #include "../../util/net/fd_ip4.h"
      12             : #include "../../util/log/fd_log.h"
      13             : #include "../keyguard/fd_keyguard.h"
      14             : 
      15             : #include "../../waltz/tlsrec/fd_tlsrec.h"
      16             : #include "../../ballet/ed25519/fd_x25519.h"
      17             : 
      18             : #define ZSTD_STATIC_LINKING_ONLY
      19             : #include <zstd.h>
      20             : 
      21             : #include <netinet/tcp.h>
      22             : #include <unistd.h>
      23             : #include <errno.h>
      24             : #include <sys/epoll.h>
      25             : #include <sys/socket.h>
      26             : #include <netinet/in.h>
      27             : 
      28           0 : #define DISCONNECT_REASON_IDENTITY_CHANGED   (0)
      29           0 : #define DISCONNECT_REASON_CONNECT_FAILED     (1)
      30           0 : #define DISCONNECT_REASON_DNS_RESOLVE_FAILED (2)
      31           0 : #define DISCONNECT_REASON_TIMEOUT            (3)
      32           0 : #define DISCONNECT_REASON_TRANSPORT_FAILED   (4)
      33           0 : #define DISCONNECT_REASON_PEER_CLOSED        (5)
      34           0 : #define DISCONNECT_REASON_INVALID_CURSOR     (6)
      35           0 : #define DISCONNECT_REASON_AUTH_FAILED        (7)
      36           0 : #define DISCONNECT_REASON_INVALID_PROTOBUF   (8)
      37             : 
      38           0 : #define FD_EVENT_CLIENT_REQ_CTX_AUTHENTICATE  (1UL)
      39           0 : #define FD_EVENT_CLIENT_REQ_CTX_STREAM_EVENTS (2UL) /* low byte; stream generation above */
      40             : 
      41           0 : #define FD_EVENT_CLIENT_HEARTBEAT_NANOS (15L*(long)1e9)
      42           0 : #define FD_EVENT_CLIENT_RESPONSE_TIMEOUT_NANOS (60L*(long)1e9)
      43             : 
      44           0 : #define FD_EVENT_CLIENT_TX_RATE_BPS (4L*1000L*1000L)
      45           0 : #define FD_EVENT_CLIENT_TX_BURST (256L<<10)
      46           0 : #define FD_EVENT_CLIENT_CREDIT_STALL_NANOS (20L*(long)1e9)
      47             : 
      48             : /* The edge proxy fails a request after 100 MB of body.  Rotate to a new
      49             :    StreamEvents request on the same connection before that.  The server
      50             :    may override the cap in its Authenticate response. */
      51           0 : #define FD_EVENT_CLIENT_STREAM_BODY_CAP     (100UL*1000UL*1000UL)
      52           0 : #define FD_EVENT_CLIENT_STREAM_BODY_MARGIN  (64UL<<10)
      53             : 
      54             : /* Sent in Authenticate; servers send newer response fields only to
      55             :    clients that advertise them.  1: max_stream_body */
      56           0 : #define FD_EVENT_CLIENT_PROTOCOL_VERSION (1UL)
      57             : 
      58             : /* StreamEventsZstd has one zstd stream per call.  Each gRPC message is
      59             :    the compressor output at one flush; decompressed, the stream is
      60             :    varint(len) | StreamEventsRequest, repeated.  A flush ends a batch
      61             :    always on an event boundary. */
      62           0 : #define FD_EVENT_CLIENT_ZSTD_LEVEL       (3)
      63           0 : #define FD_EVENT_CLIENT_ZSTD_WLOG        (20)
      64           0 : #define FD_EVENT_CLIENT_ZSTD_BATCH       (64UL<<10)
      65           0 : #define FD_EVENT_CLIENT_ZSTD_FLUSH_NANOS (10L*1000L*1000L)
      66             : FD_STATIC_ASSERT( FD_EVENT_CLIENT_ZSTD_LEVEL<=3 || FD_EVENT_CLIENT_ZSTD_LEVEL>=16, zstd_level );
      67           0 : #define FD_EVENT_CLIENT_STREAM_DRAIN_NANOS (30L*(long)1e9)
      68             : 
      69           0 : #define FD_EVENT_CLIENT_TOKEN_SZ (217UL)
      70             : 
      71             : struct fd_event_client {
      72             :   fd_grpc_client_t * grpc_client;
      73             :   fd_grpc_client_metrics_t grpc_metrics[1];
      74             :   fd_grpc_h2_stream_t * event_stream;
      75             : 
      76             :   char client_version[ 10UL ];
      77             :   char commit_hash[ 41UL ];
      78             :   char action[ 16UL ];
      79             :   uchar identity_pubkey[ 32UL ];
      80             : 
      81             :   int       has_genesis_hash;
      82             :   fd_hash_t genesis_hash[1];
      83             : 
      84             :   int connect_fail_logged;
      85             : 
      86             :   ushort has_shred_version;
      87             :   ushort shred_version;
      88             : 
      89             :   ulong event_id;
      90             : 
      91             :   ulong instance_id;
      92             :   ulong boot_id;
      93             :   ulong machine_id;
      94             : 
      95             :   int defer_disconnect;
      96             :   ulong consecutive_failure_count;
      97             : 
      98             :   long now; /* of the current poll */
      99             : 
     100             :   long last_stream_send_ns;
     101             :   long last_response_ns;
     102             : 
     103             :   long  tx_tokens;
     104             :   long  tx_tokens_ns;
     105             :   long  stall_since;
     106             :   ulong stall_rem;
     107             :   ulong stall_events_sent;
     108             : 
     109             :   int auth_send_pending;
     110             : 
     111             :   ulong stream_body_max; /* rotate before a stream's request body exceeds this */
     112             : 
     113             :   ZSTD_CCtx *    zst;
     114             :   ZSTD_outBuffer zout;
     115             :   ulong          zbatch_raw;
     116             :   ulong          zbatch_cnt;
     117             :   long           zbatch_ns;
     118             :   int            zframe_open; /* frame started, epilogue not yet sent */
     119             : 
     120             :   ulong stream_gen;   /* generation of event_stream, in its request_ctx */
     121             :   ulong stream_tx_sz; /* request body bytes sent on event_stream */
     122             :   fd_grpc_h2_stream_t * drain_stream; /* half-closed stream, NULL once the server ends it */
     123             :   ulong drain_gen;    /* generation of the half-closed stream, 0 if none */
     124             :   ulong drain_last;   /* last nonce sent on the half-closed stream */
     125             :   ulong held_ack;     /* newer stream's ack held until the drain ends, ULONG_MAX if none */
     126             : 
     127             :   ulong state;
     128             :   union {
     129             :     struct {
     130             :       long reconnect_deadline;
     131             :     } disconnected;
     132             : 
     133             :     struct {
     134             :       long connect_deadline;
     135             :     } connecting;
     136             : 
     137             :     struct {
     138             :       long connected_timestamp;
     139             :     } connected;
     140             :   };
     141             : 
     142             :   int so_sndbuf;
     143             :   int sockfd;
     144             : 
     145             :   int epoll_fd;
     146             :   int epoll_out_armed;
     147             : 
     148             :   int    use_tls;
     149             :   fd_tls_t         tls[1];
     150             :   fd_chacha_rng_t  tls_rng[1];
     151             :   fd_tlsrec_conn_t tls_conn[1];
     152             : 
     153             :   /* wallclock deadline for auth handshake, LONG_MAX if not
     154             :      authenticating. */
     155             :   long auth_deadline;
     156             : 
     157             :   char   server_fqdn[ FD_FQDN_BUF_MAX ]; /* cstr */
     158             :   ulong  server_fqdn_len;
     159             :   uint   server_ip4_addr;
     160             :   ushort server_tcp_port;
     161             : 
     162             :   fd_rng_t * rng;
     163             :   fd_circq_t * circq;
     164             :   fd_keyguard_client_t * keyguard_client;
     165             : 
     166             :   /* Stateless-auth bearer value: "hex(challenge_token).hex(signature)",
     167             :      built from the challenge token returned by Authenticate and our
     168             :      ed25519 signature over it.  Presented as the `authorization: Bearer
     169             :      <...>` header on the StreamEvents request. */
     170             :   char  auth_bearer[ 2UL*FD_EVENT_CLIENT_TOKEN_SZ + 1UL + 2UL*64UL + 1UL ];
     171             :   ulong auth_bearer_len;
     172             : 
     173             :   fd_event_client_metrics_t metrics;
     174             : };
     175             : 
     176             : 
     177             : static void
     178             : tls_init( fd_event_client_t *        client,
     179           0 :           fd_x509_ca_store_t const * ca_store ) {
     180           0 :   fd_tls_t * tls = fd_tls_join( fd_tls_new( client->tls ) );
     181           0 :   FD_TEST( tls );
     182             : 
     183           0 :   uchar rng_key[ FD_CHACHA_KEY_SZ ];
     184           0 :   if( FD_UNLIKELY( !fd_rng_secure( rng_key, sizeof(rng_key) ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
     185           0 :   fd_chacha_rng_init( client->tls_rng, rng_key, FD_CHACHA_RNG_ALGO_CHACHA8 );
     186           0 :   fd_memzero_explicit( rng_key, sizeof(rng_key) );
     187           0 :   tls->rng = client->tls_rng;
     188             : 
     189           0 :   static uchar const alpn[] = { 2, 'h', '2' };
     190           0 :   fd_memcpy( tls->alpn, alpn, sizeof(alpn) );
     191           0 :   tls->alpn_sz = sizeof(alpn);
     192             : 
     193           0 :   if( FD_UNLIKELY( client->server_fqdn_len>=sizeof(tls->server_name) ) ) {
     194           0 :     FD_LOG_ERR(( "Server name is %lu bytes, longer than the %lu byte maximum: check [tiles.event.url]",
     195           0 :                  client->server_fqdn_len, sizeof(tls->server_name)-1UL ));
     196           0 :   }
     197           0 :   fd_memcpy( tls->server_name, client->server_fqdn, client->server_fqdn_len+1UL );
     198           0 :   tls->server_name_len = (ushort)client->server_fqdn_len;
     199             : 
     200           0 :   tls->ca_store = ca_store;
     201           0 : }
     202             : 
     203             : FD_FN_CONST ulong
     204           0 : fd_event_client_align( void ) {
     205           0 :   return alignof( fd_event_client_t );
     206           0 : }
     207             : 
     208             : static ulong
     209           0 : zstd_footprint( void ) {
     210           0 :   ZSTD_compressionParameters cp = ZSTD_getCParams( FD_EVENT_CLIENT_ZSTD_LEVEL, ZSTD_CONTENTSIZE_UNKNOWN, 0UL );
     211           0 :   cp.windowLog = FD_EVENT_CLIENT_ZSTD_WLOG;
     212           0 :   return ZSTD_estimateCStreamSize_usingCParams( cp );
     213           0 : }
     214             : 
     215             : ulong
     216           0 : fd_event_client_footprint( ulong buf_max ) {
     217           0 :   ulong l;
     218           0 :   l = FD_LAYOUT_INIT;
     219           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_event_client_t), sizeof(fd_event_client_t)           );
     220           0 :   l = FD_LAYOUT_APPEND( l, fd_grpc_client_align(),     fd_grpc_client_footprint( buf_max ) );
     221           0 :   l = FD_LAYOUT_APPEND( l, 64UL,                       zstd_footprint()                    );
     222           0 :   return FD_LAYOUT_FINI( l, alignof(fd_event_client_t) );
     223           0 : }
     224             : 
     225             : void *
     226             : fd_event_client_new( void *                     shmem,
     227             :                      fd_keyguard_client_t *     keyguard_client,
     228             :                      fd_rng_t *                 rng,
     229             :                      fd_circq_t *               circq,
     230             :                      int                        epoll_fd,
     231             :                      int                        so_sndbuf,
     232             :                      char const *               _url,
     233             :                      uchar const *              identity_pubkey,
     234             :                      char const *               client_version,
     235             :                      char const *               commit_hash,
     236             :                      char const *               action,
     237             :                      ulong                      instance_id,
     238             :                      ulong                      boot_id,
     239             :                      ulong                      machine_id,
     240             :                      ulong                      buf_max,
     241             :                      int                        use_tls,
     242           0 :                      fd_x509_ca_store_t const * ca_store ) {
     243           0 :   if( FD_UNLIKELY( !shmem ) ) {
     244           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     245           0 :     return NULL;
     246           0 :   }
     247             : 
     248           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_event_client_align() ) ) ) {
     249           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     250           0 :     return NULL;
     251           0 :   }
     252             : 
     253           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     254           0 :   fd_event_client_t * client = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_event_client_t), sizeof(fd_event_client_t)           );
     255           0 :   void * grpc_client_mem     = FD_SCRATCH_ALLOC_APPEND( l, fd_grpc_client_align(),     fd_grpc_client_footprint( buf_max ) );
     256           0 :   void * zst_mem             = FD_SCRATCH_ALLOC_APPEND( l, 64UL,                       zstd_footprint()                    );
     257             : 
     258           0 :   fd_url_t url[1];
     259           0 :   _Bool _is_ssl = 0;
     260           0 :   if( FD_UNLIKELY( fd_url_parse_endpoint( url,
     261           0 :                                           _url,
     262           0 :                                           strlen( _url ),
     263           0 :                                           &client->server_tcp_port,
     264           0 :                                           &_is_ssl,
     265           0 :                                           "[tiles.event.url]" ) ) ) {
     266           0 :     FD_LOG_ERR(( "Could not parse [tiles.event.url]" ));
     267           0 :   }
     268           0 :   if( FD_UNLIKELY( url->host_len>=FD_FQDN_BUF_MAX ) ) {
     269           0 :     FD_LOG_CRIT(( "Invalid url->host_len" )); /* unreachable */
     270           0 :   }
     271           0 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( client->server_fqdn ), url->host, url->host_len ) );
     272           0 :   client->server_fqdn_len = url->host_len;
     273             : 
     274           0 :   fd_memcpy( client->identity_pubkey, identity_pubkey, 32UL );
     275           0 :   fd_cstr_ncpy( client->client_version, client_version, sizeof( client->client_version ) );
     276           0 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( client->commit_hash ), commit_hash, fd_ulong_min( strlen( commit_hash ), sizeof( client->commit_hash )-1UL ) ) );
     277           0 :   fd_cstr_ncpy( client->action, action, sizeof( client->action ) );
     278             : 
     279           0 :   client->event_id = 0UL;
     280             : 
     281           0 :   client->instance_id = instance_id;
     282           0 :   client->boot_id     = boot_id;
     283           0 :   client->machine_id  = machine_id;
     284             : 
     285           0 :   client->has_genesis_hash = 0;
     286           0 :   client->has_shred_version = 0;
     287           0 :   client->connect_fail_logged = 0;
     288             : 
     289           0 :   client->so_sndbuf = so_sndbuf;
     290           0 :   client->sockfd = -1;
     291           0 :   FD_TEST( -1!=epoll_fd );
     292           0 :   client->epoll_fd = epoll_fd;
     293           0 :   client->use_tls = use_tls;
     294           0 :   if( use_tls ) {
     295           0 :     if( FD_UNLIKELY( !ca_store ) ) FD_LOG_ERR(( "NULL ca_store, but [tiles.event.url] requires TLS" ));
     296           0 :     tls_init( client, ca_store );
     297           0 :   }
     298           0 :   client->auth_deadline = LONG_MAX;
     299           0 :   client->auth_send_pending = 0;
     300           0 :   client->stream_body_max = FD_EVENT_CLIENT_STREAM_BODY_CAP-FD_EVENT_CLIENT_STREAM_BODY_MARGIN;
     301           0 :   client->zst = ZSTD_initStaticCStream( zst_mem, zstd_footprint() );
     302           0 :   FD_TEST( client->zst );
     303           0 :   FD_TEST( !ZSTD_isError( ZSTD_CCtx_setParameter( client->zst, ZSTD_c_compressionLevel, FD_EVENT_CLIENT_ZSTD_LEVEL ) ) );
     304           0 :   FD_TEST( !ZSTD_isError( ZSTD_CCtx_setParameter( client->zst, ZSTD_c_windowLog,        FD_EVENT_CLIENT_ZSTD_WLOG  ) ) );
     305           0 :   client->zbatch_raw   = 0UL;
     306           0 :   client->zbatch_cnt   = 0UL;
     307           0 :   client->zframe_open  = 0;
     308           0 :   client->stream_gen   = 0UL;
     309           0 :   client->stream_tx_sz = 0UL;
     310           0 :   client->drain_stream = NULL;
     311           0 :   client->drain_gen    = 0UL;
     312           0 :   client->held_ack     = ULONG_MAX;
     313           0 :   client->state = FD_EVENT_CLIENT_STATE_DISCONNECTED;
     314           0 :   client->disconnected.reconnect_deadline = 0L;
     315             : 
     316           0 :   client->defer_disconnect = INT_MAX;
     317           0 :   client->consecutive_failure_count = 7UL; /* Start high, so if server is down we don't keep retrying on boot */
     318           0 :   client->now                  = 0L;
     319           0 :   client->last_stream_send_ns  = 0L;
     320           0 :   client->last_response_ns     = 0L;
     321           0 :   client->tx_tokens            = FD_EVENT_CLIENT_TX_BURST;
     322           0 :   client->tx_tokens_ns         = 0L;
     323           0 :   client->stall_since          = 0L;
     324             : 
     325           0 :   client->circq = circq;
     326           0 :   client->rng = rng;
     327           0 :   client->keyguard_client = keyguard_client;
     328             : 
     329           0 :   extern fd_grpc_client_callbacks_t fd_event_client_grpc_callbacks;
     330           0 :   client->grpc_client = fd_grpc_client_new( grpc_client_mem, &fd_event_client_grpc_callbacks, client->grpc_metrics, client, buf_max, fd_rng_ulong( rng ) );
     331           0 :   FD_TEST( client->grpc_client );
     332             : 
     333           0 :   memset( &client->metrics, 0, sizeof(client->metrics) );
     334           0 :   memset( client->grpc_metrics, 0, sizeof(fd_grpc_client_metrics_t) );
     335             : 
     336           0 :   fd_grpc_client_set_version( client->grpc_client, client->client_version, strlen( client->client_version ) );
     337           0 :   fd_grpc_client_set_authority( client->grpc_client, client->server_fqdn, client->server_fqdn_len, client->server_tcp_port );
     338             : 
     339           0 :   return (void *)client;
     340           0 : }
     341             : 
     342             : fd_event_client_t *
     343           0 : fd_event_client_join( void * shec ) {
     344           0 :   if( FD_UNLIKELY( !shec ) ) {
     345           0 :     FD_LOG_WARNING(( "NULL shec" ));
     346           0 :     return NULL;
     347           0 :   }
     348             : 
     349           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shec, fd_event_client_align() ) ) ) {
     350           0 :     FD_LOG_WARNING(( "misaligned shec" ));
     351           0 :     return NULL;
     352           0 :   }
     353             : 
     354           0 :   fd_event_client_t * client = (fd_event_client_t *)shec;
     355             : 
     356           0 :   return client;
     357           0 : }
     358             : 
     359             : fd_event_client_metrics_t const *
     360           0 : fd_event_client_metrics( fd_event_client_t const * client ) {
     361             :   /* Update bytes from grpc metrics */
     362           0 :   ((fd_event_client_t *)client)->metrics.bytes_written = client->grpc_metrics->stream_chunks_tx_bytes;
     363           0 :   ((fd_event_client_t *)client)->metrics.bytes_read = client->grpc_metrics->stream_chunks_rx_bytes;
     364           0 :   return &client->metrics;
     365           0 : }
     366             : 
     367             : ulong
     368           0 : fd_event_client_state( fd_event_client_t const * client ) {
     369           0 :   return client->state;
     370           0 : }
     371             : 
     372             : ulong
     373           0 : fd_event_client_id_reserve( fd_event_client_t * client ) {
     374           0 :   return client->event_id++;
     375           0 : }
     376             : 
     377             : void
     378             : fd_event_client_init_genesis( fd_event_client_t *       client,
     379           0 :                               fd_genesis_meta_t const * meta ) {
     380           0 :   *client->genesis_hash = meta->genesis_hash;
     381           0 :   client->has_genesis_hash = 1;
     382           0 : }
     383             : 
     384             : void
     385             : fd_event_client_init_shred_version( fd_event_client_t * client,
     386           0 :                                     ushort              shred_version ) {
     387           0 :   client->shred_version = shred_version;
     388           0 :   client->has_shred_version = 1;
     389           0 : }
     390             : 
     391             : static void
     392             : backoff( fd_event_client_t * client,
     393           0 :          long                now ) {
     394           0 :   ulong backoff_base = 1UL << fd_ulong_min( client->consecutive_failure_count, 7UL ); /* max 4 mins */
     395           0 :   ulong backoff_jitter = fd_rng_ulong_roll( client->rng, backoff_base );
     396           0 :   client->disconnected.reconnect_deadline = now + (long)( backoff_base + backoff_jitter )*(long)1e9;
     397           0 :   if( FD_UNLIKELY( client->consecutive_failure_count < 8UL ) ) client->consecutive_failure_count++;
     398           0 : }
     399             : 
     400             : static void
     401             : disconnect( fd_event_client_t * client,
     402             :             long                now,
     403             :             int                 reason,
     404             :             int                 err,
     405           0 :             int                 _backoff ) {
     406           0 :   if( FD_LIKELY( -1!=client->sockfd ) ) {
     407           0 :     if( FD_UNLIKELY( -1==close( client->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
     408           0 :     client->sockfd = -1;
     409           0 :     client->epoll_out_armed = 0;
     410           0 :     client->state = FD_EVENT_CLIENT_STATE_DISCONNECTED;
     411           0 :     fd_circq_reset_cursor( client->circq );
     412           0 :   }
     413             : 
     414           0 :   client->event_stream = NULL;
     415           0 :   client->defer_disconnect = INT_MAX;
     416           0 :   client->auth_deadline = LONG_MAX;
     417           0 :   client->auth_send_pending = 0;
     418           0 :   client->stall_since = 0L;
     419           0 :   client->stream_tx_sz = 0UL;
     420           0 :   client->zbatch_raw   = 0UL;
     421           0 :   client->zbatch_cnt   = 0UL;
     422           0 :   client->zframe_open  = 0;
     423           0 :   client->drain_stream = NULL;
     424           0 :   client->drain_gen    = 0UL;
     425           0 :   client->held_ack     = ULONG_MAX;
     426             : 
     427           0 :   client->auth_bearer[ 0 ] = '\0';
     428           0 :   client->auth_bearer_len  = 0UL;
     429             : 
     430           0 :   switch( reason ) {
     431           0 :     case DISCONNECT_REASON_IDENTITY_CHANGED:
     432           0 :       FD_LOG_INFO(( "disconnected: identity changed" ));
     433           0 :       break;
     434           0 :     case DISCONNECT_REASON_CONNECT_FAILED:
     435           0 :       if( FD_UNLIKELY( !client->connect_fail_logged ) ) FD_LOG_WARNING(( "connecting to telemetry server " FD_IP4_ADDR_FMT ":%u failed %s(%i-%s)%s", FD_IP4_ADDR_FMT_ARGS( client->server_ip4_addr ), client->server_tcp_port, fd_log_style_dim(), errno, fd_io_strerror( errno ), fd_log_style_normal() ));
     436           0 :       else                                              FD_LOG_INFO((    "connecting to telemetry server " FD_IP4_ADDR_FMT ":%u failed (%i-%s)", FD_IP4_ADDR_FMT_ARGS( client->server_ip4_addr ), client->server_tcp_port, errno, fd_io_strerror( errno ) ));
     437           0 :       client->connect_fail_logged = 1;
     438           0 :       client->metrics.transport_fail_cnt++;
     439           0 :       break;
     440           0 :     case DISCONNECT_REASON_DNS_RESOLVE_FAILED:
     441           0 :       if( FD_UNLIKELY( !client->connect_fail_logged ) ) FD_LOG_WARNING(( "failed to resolve telemetry server host %.*s %s(%d-%s)%s", (int)client->server_fqdn_len, client->server_fqdn, fd_log_style_dim(), err, fd_gai_strerror( err ), fd_log_style_normal() ));
     442           0 :       else                                              FD_LOG_INFO((    "failed to resolve telemetry server host %.*s (%d-%s)", (int)client->server_fqdn_len, client->server_fqdn, err, fd_gai_strerror( err ) ));
     443           0 :       client->connect_fail_logged = 1;
     444           0 :       client->metrics.transport_fail_cnt++;
     445           0 :       break;
     446           0 :     case DISCONNECT_REASON_TIMEOUT:
     447           0 :       FD_LOG_INFO(( "connection failed: timeout" ));
     448           0 :       client->metrics.transport_fail_cnt++;
     449           0 :       break;
     450           0 :     case DISCONNECT_REASON_TRANSPORT_FAILED:
     451           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: transport failed %s(%d-%s)%s", fd_log_style_dim(), err, fd_io_strerror( err ), fd_log_style_normal() ));
     452           0 :       client->metrics.transport_fail_cnt++;
     453           0 :       break;
     454           0 :     case DISCONNECT_REASON_PEER_CLOSED:
     455           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: peer closed connection" ));
     456           0 :       client->metrics.transport_fail_cnt++;
     457           0 :       break;
     458           0 :     case DISCONNECT_REASON_INVALID_CURSOR:
     459           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: invalid cursor" ));
     460           0 :       client->metrics.transport_fail_cnt++;
     461           0 :       break;
     462           0 :     case DISCONNECT_REASON_AUTH_FAILED:
     463           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: authentication failed" ));
     464           0 :       client->metrics.transport_fail_cnt++;
     465           0 :       break;
     466           0 :     case DISCONNECT_REASON_INVALID_PROTOBUF:
     467           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: invalid protobuf message received" ));
     468           0 :       client->metrics.transport_fail_cnt++;
     469           0 :       break;
     470           0 :     default:
     471           0 :       FD_LOG_WARNING(( "disconnected from telemetry server: unknown reason %d", reason ));
     472           0 :       client->metrics.transport_fail_cnt++;
     473           0 :       break;
     474           0 :   }
     475             : 
     476           0 :   if( FD_LIKELY( _backoff ) ) backoff( client, now );
     477           0 : }
     478             : 
     479             : void
     480             : fd_event_client_set_identity( fd_event_client_t * client,
     481           0 :                               uchar const *       identity_pubkey ) {
     482           0 :   fd_memcpy( client->identity_pubkey, identity_pubkey, 32UL );
     483           0 :   disconnect( client, client->now, DISCONNECT_REASON_IDENTITY_CHANGED, 0, 0 );
     484           0 : }
     485             : 
     486             : static void
     487             : reconnect( fd_event_client_t * client,
     488             :            long                now,
     489           0 :            int *               charge_busy ) {
     490           0 :   FD_TEST( client->state==FD_EVENT_CLIENT_STATE_DISCONNECTED );
     491             : 
     492           0 :   if( FD_UNLIKELY( now<client->disconnected.reconnect_deadline ) ) return;
     493             : 
     494           0 :   *charge_busy = 1;
     495           0 :   client->metrics.connect_attempt_cnt++;
     496             : 
     497           0 :   FD_LOG_INFO(( "connecting to event server %s://%.*s:%u", client->use_tls ? "https" : "http", (int)client->server_fqdn_len, client->server_fqdn, client->server_tcp_port ));
     498             : 
     499             :   /* FIXME IPv6 support */
     500           0 :   fd_addrinfo_t hints = {0};
     501           0 :   hints.ai_family = AF_INET;
     502           0 :   fd_addrinfo_t * res = NULL;
     503           0 :   uchar scratch[ 4096 ];
     504           0 :   void * pscratch = scratch;
     505           0 :   int err = fd_getaddrinfo( client->server_fqdn, &hints, &res, &pscratch, sizeof(scratch) );
     506           0 :   if( FD_UNLIKELY( err ) ) {
     507           0 :     disconnect( client, now, DISCONNECT_REASON_DNS_RESOLVE_FAILED, err, 1 );
     508           0 :     return;
     509           0 :   }
     510             : 
     511           0 :   if( FD_UNLIKELY( !res || !res->ai_addr ) ) {
     512           0 :     disconnect( client, now, DISCONNECT_REASON_DNS_RESOLVE_FAILED, 0, 1 );
     513           0 :     return;
     514           0 :   }
     515             : 
     516           0 :   uint const ip4_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr;
     517           0 :   client->server_ip4_addr = ip4_addr;
     518             : 
     519           0 :   client->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     520           0 :   if( FD_UNLIKELY( -1==client->sockfd ) ) FD_LOG_ERR(( "socket() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
     521             : 
     522           0 :   struct epoll_event ev = { .events = EPOLLIN|EPOLLOUT, .data.fd = client->sockfd };
     523           0 :   if( FD_UNLIKELY( -1==epoll_ctl( client->epoll_fd, EPOLL_CTL_ADD, client->sockfd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(ADD,sockfd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     524           0 :   client->epoll_out_armed = 1;
     525             : 
     526           0 :   struct sockaddr_in addr;
     527           0 :   fd_memset( &addr, 0, sizeof( addr ) );
     528           0 :   addr.sin_family = AF_INET;
     529           0 :   addr.sin_port   = fd_ushort_bswap( client->server_tcp_port );
     530           0 :   addr.sin_addr.s_addr = ip4_addr;
     531             : 
     532           0 :   int tcp_nodelay = 1;
     533           0 :   if( FD_UNLIKELY( -1==setsockopt( client->sockfd, SOL_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int) ) ) ) FD_LOG_ERR(( "setsockopt failed (%d-%s)", errno, fd_io_strerror( errno ) ));
     534           0 :   if( FD_UNLIKELY( -1==setsockopt( client->sockfd, SOL_SOCKET, SO_SNDBUF, &client->so_sndbuf, sizeof(int) ) ) ) FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_SNDBUF,%i) failed (%i-%s)", client->so_sndbuf, errno, fd_io_strerror( errno ) ));
     535             : 
     536           0 :   if( FD_UNLIKELY( -1==connect( client->sockfd, fd_type_pun_const( &addr ), sizeof(struct sockaddr_in) ) && errno!=EINPROGRESS ) ) {
     537           0 :     disconnect( client, now, DISCONNECT_REASON_CONNECT_FAILED, errno, 1 );
     538           0 :     return;
     539           0 :   }
     540             : 
     541           0 :   if( client->use_tls ) {
     542           0 :     fd_tls_t * tls = client->tls;
     543           0 :     if( FD_UNLIKELY( !fd_rng_secure( tls->kex_private_key, 32UL ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
     544           0 :     fd_x25519_public( tls->kex_public_key, tls->kex_private_key );
     545           0 :     fd_tlsrec_conn_init( client->tls_conn, tls, 0 );
     546           0 :   }
     547             : 
     548           0 :   fd_grpc_client_reset( client->grpc_client );
     549             : 
     550           0 :   client->state = FD_EVENT_CLIENT_STATE_CONNECTING;
     551           0 :   client->connecting.connect_deadline = now+(long)1L*(long)1e9; /* 1 second to connect */
     552           0 : }
     553             : 
     554             : static int
     555             : fd_event_client_try_send_authenticate( fd_event_client_t * client,
     556           0 :                                        long                now ) {
     557           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( client->grpc_client ) ) ) return 0;
     558           0 :   if( FD_UNLIKELY( fd_grpc_client_request_stream_busy( client->grpc_client ) ) ) return 0;
     559             : 
     560           0 :   fd_pb_encoder_t auth_req[1];
     561           0 :   uchar buffer[ 256UL ];
     562           0 :   fd_pb_encoder_init( auth_req, buffer, sizeof(buffer) );
     563             : 
     564           0 :   fd_pb_push_bytes( auth_req, 1U, client->identity_pubkey, 32UL );
     565           0 :   fd_pb_push_string( auth_req, 2U, client->client_version, strlen( client->client_version ) );
     566           0 :   fd_pb_push_string( auth_req, 3U, client->commit_hash, strlen( client->commit_hash ) );
     567           0 :   fd_pb_push_bytes( auth_req, 4U, client->genesis_hash, 32UL );
     568           0 :   fd_pb_push_uint64( auth_req, 5U, client->shred_version );
     569           0 :   fd_pb_push_uint64( auth_req, 6U, client->instance_id );
     570           0 :   fd_pb_push_uint64( auth_req, 7U, client->machine_id );
     571           0 :   fd_pb_push_uint64( auth_req, 8U, client->boot_id );
     572           0 :   fd_pb_push_string( auth_req, 9U, client->action, strlen( client->action ) );
     573           0 :   fd_pb_push_uint64( auth_req, 10U, FD_EVENT_CLIENT_PROTOCOL_VERSION );
     574             : 
     575           0 :   fd_grpc_h2_stream_t * stream = fd_grpc_client_request_start1(
     576           0 :       client->grpc_client,
     577           0 :       "/events.v1.EventService/Authenticate", strlen("/events.v1.EventService/Authenticate"),
     578           0 :       FD_EVENT_CLIENT_REQ_CTX_AUTHENTICATE,
     579           0 :       buffer, fd_pb_encoder_out_sz( auth_req ),
     580           0 :       NULL, 0UL,
     581           0 :       0 /* not streaming */ );
     582             : 
     583           0 :   if( FD_UNLIKELY( !stream ) ) return 0;
     584             : 
     585           0 :   fd_grpc_client_deadline_set( stream, FD_GRPC_DEADLINE_HEADER, now+(long)2e9 );
     586           0 :   fd_grpc_client_deadline_set( stream, FD_GRPC_DEADLINE_RX_END, now+(long)2e9 );
     587             : 
     588           0 :   client->auth_send_pending = 0;
     589           0 :   FD_LOG_INFO(( "Requesting auth challenge from event server " FD_IP4_ADDR_FMT ":%u (%.*s)",
     590           0 :                 FD_IP4_ADDR_FMT_ARGS( client->server_ip4_addr ), client->server_tcp_port,
     591           0 :                 (int)client->server_fqdn_len, client->server_fqdn ));
     592           0 :   return 1;
     593           0 : }
     594             : 
     595             : static void
     596           0 : fd_event_client_grpc_conn_established( void * app_ctx ) {
     597           0 :   fd_event_client_t * client = app_ctx;
     598             : 
     599           0 :   long now = client->now;
     600           0 :   client->state             = FD_EVENT_CLIENT_STATE_AUTHENTICATING;
     601           0 :   client->auth_deadline     = now + (long)2e9;
     602           0 :   client->auth_send_pending = 1;
     603             : 
     604           0 :   fd_event_client_try_send_authenticate( client, now );
     605           0 : }
     606             : 
     607             : static void
     608             : fd_event_client_handle_auth_challenge_resp( fd_event_client_t * client,
     609             :                                             void const *        protobuf,
     610           0 :                                             ulong               protobuf_sz ) {
     611           0 :   fd_pb_inbuf_t inbuf[1];
     612           0 :   fd_pb_inbuf_init( inbuf, protobuf, protobuf_sz );
     613             : 
     614             :   /* challenge (1) and optional max_stream_body (2), in any order */
     615           0 :   uchar const * challenge = NULL;
     616           0 :   ulong         body_cap  = FD_EVENT_CLIENT_STREAM_BODY_CAP;
     617           0 :   while( fd_pb_inbuf_sz( inbuf ) ) {
     618           0 :     fd_pb_tlv_t tlv;
     619           0 :     if( FD_UNLIKELY( !fd_pb_read_tlv( inbuf, &tlv ) ) ) {
     620           0 :       FD_LOG_WARNING(( "Failed to parse auth challenge response" ));
     621           0 :       client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     622           0 :       return;
     623           0 :     }
     624           0 :     if( tlv.field_id==1U && tlv.wire_type==FD_PB_WIRE_TYPE_LEN ) {
     625           0 :       if( FD_UNLIKELY( tlv.len!=FD_EVENT_CLIENT_TOKEN_SZ || fd_pb_inbuf_sz( inbuf )<tlv.len ) ) {
     626           0 :         FD_LOG_WARNING(( "Invalid challenge token size: %lu bytes (expected %lu)", tlv.len, FD_EVENT_CLIENT_TOKEN_SZ ));
     627           0 :         client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     628           0 :         return;
     629           0 :       }
     630           0 :       challenge = inbuf->cur;
     631           0 :       inbuf->cur += tlv.len;
     632           0 :     } else if( tlv.field_id==2U && tlv.wire_type==FD_PB_WIRE_TYPE_VARINT ) {
     633           0 :       body_cap = fd_ulong_min( tlv.varint, UINT_MAX );
     634           0 :     } else {
     635           0 :       FD_LOG_WARNING(( "Unexpected field in auth challenge response" ));
     636           0 :       client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     637           0 :       return;
     638           0 :     }
     639           0 :   }
     640           0 :   if( FD_UNLIKELY( !challenge ) ) {
     641           0 :     FD_LOG_WARNING(( "Missing challenge in auth challenge response" ));
     642           0 :     client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     643           0 :     return;
     644           0 :   }
     645           0 :   if( FD_UNLIKELY( body_cap<client->grpc_client->nanopb_tx_max+FD_EVENT_CLIENT_STREAM_BODY_MARGIN ) ) {
     646           0 :     FD_LOG_WARNING(( "telemetry server stream body cap %lu is below the largest event (%lu)", body_cap, client->grpc_client->nanopb_tx_max ));
     647           0 :     client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     648           0 :     return;
     649           0 :   }
     650           0 :   client->stream_body_max = body_cap-FD_EVENT_CLIENT_STREAM_BODY_MARGIN;
     651             : 
     652           0 :   ulong challenge_len = FD_EVENT_CLIENT_TOKEN_SZ;
     653           0 :   uchar challenge_token[ FD_EVENT_CLIENT_TOKEN_SZ ];
     654           0 :   memcpy( challenge_token, challenge, challenge_len );
     655             : 
     656           0 :   uchar sign_request[ 100UL + FD_EVENT_CLIENT_TOKEN_SZ ];
     657           0 :   static char const sign_prefix[ 100 ] =
     658           0 :     "                                "  /* 32 spaces */
     659           0 :     "                                "  /* 32 spaces */
     660           0 :     "Firedancer event challenge-response";
     661           0 :   memcpy( sign_request,     sign_prefix,     sizeof(sign_prefix) );
     662           0 :   memcpy( sign_request+100, challenge_token, challenge_len       );
     663             : 
     664           0 :   uchar signature[ 64UL ];
     665           0 :   fd_keyguard_client_sign( client->keyguard_client,
     666           0 :                            signature,
     667           0 :                            sign_request, 100UL+challenge_len,
     668           0 :                            FD_KEYGUARD_SIGN_TYPE_ED25519 );
     669             : 
     670             :   /* Build "hex(challenge_token).hex(signature)" for the bearer token. */
     671           0 :   fd_hex_encode( client->auth_bearer, challenge_token, FD_EVENT_CLIENT_TOKEN_SZ );
     672           0 :   client->auth_bearer[ 2UL*FD_EVENT_CLIENT_TOKEN_SZ ] = '.';
     673           0 :   fd_hex_encode( client->auth_bearer + 2UL*FD_EVENT_CLIENT_TOKEN_SZ+1UL, signature, 64UL );
     674           0 :   client->auth_bearer_len = 2UL*FD_EVENT_CLIENT_TOKEN_SZ + 1UL + 2UL*64UL;
     675           0 :   client->auth_bearer[ client->auth_bearer_len ] = '\0';
     676             : 
     677           0 :   if( FD_LIKELY( client->state==FD_EVENT_CLIENT_STATE_CONNECTED ) ) return; /* stream rotation */
     678             : 
     679           0 :   client->event_stream = NULL;
     680           0 :   client->metrics.transport_success_cnt++;
     681           0 :   client->state = FD_EVENT_CLIENT_STATE_CONNECTED;
     682           0 :   client->connected.connected_timestamp = client->now;
     683           0 :   client->connect_fail_logged = 0;
     684           0 :   FD_LOG_NOTICE(( "connected to telemetry server %s%s://%.*s:%u%s",
     685           0 :                   fd_log_style_bold(), client->use_tls ? "https" : "http",
     686           0 :                   (int)client->server_fqdn_len, client->server_fqdn, client->server_tcp_port,
     687           0 :                   fd_log_style_normal() ));
     688           0 : }
     689             : 
     690             : static void
     691             : fd_event_client_grpc_conn_dead( void * app_ctx,
     692             :                                 uint   h2_err,
     693           0 :                                 int    closed_by ) {
     694           0 :   fd_event_client_t * client = app_ctx;
     695           0 :   FD_LOG_WARNING(( "telemetry connection closed %s %s(%u-%s)%s",
     696           0 :                    closed_by ? "by peer" : "due to error",
     697           0 :                    fd_log_style_dim(), h2_err, fd_h2_strerror( h2_err ), fd_log_style_normal() ));
     698           0 :   client->defer_disconnect = DISCONNECT_REASON_PEER_CLOSED;
     699           0 : }
     700             : 
     701             : static void
     702             : fd_event_client_grpc_tx_complete( void * app_ctx,
     703           0 :                                   ulong  request_ctx ) {
     704           0 :   (void)app_ctx; (void)request_ctx;
     705           0 : }
     706             : 
     707             : void
     708             : fd_event_client_grpc_rx_start( void * app_ctx,
     709           0 :                                ulong  request_ctx ) {
     710           0 :   (void)app_ctx; (void)request_ctx;
     711           0 : }
     712             : 
     713             : static void
     714             : ack_apply( fd_event_client_t * client,
     715           0 :            ulong               nonce_ack ) {
     716           0 :   client->metrics.last_acked_id = nonce_ack;
     717           0 :   if( FD_UNLIKELY( -1==fd_circq_pop_until( client->circq, nonce_ack ) ) ) {
     718           0 :     FD_LOG_WARNING(( "Event gRPC rx msg: invalid cursor ack %lu", nonce_ack ));
     719           0 :     client->defer_disconnect = DISCONNECT_REASON_INVALID_CURSOR;
     720           0 :   }
     721           0 : }
     722             : 
     723             : static void
     724           0 : drain_end( fd_event_client_t * client ) {
     725           0 :   client->drain_gen = 0UL;
     726           0 :   if( FD_UNLIKELY( client->held_ack!=ULONG_MAX ) ) ack_apply( client, client->held_ack );
     727           0 :   client->held_ack = ULONG_MAX;
     728           0 : }
     729             : 
     730             : static void
     731             : fd_event_client_handle_stream_events_resp( fd_event_client_t * client,
     732             :                                            void const *        protobuf,
     733             :                                            ulong               protobuf_sz,
     734           0 :                                            ulong               request_ctx ) {
     735           0 :   fd_pb_inbuf_t inbuf[1];
     736           0 :   fd_pb_inbuf_init( inbuf, protobuf, protobuf_sz );
     737             : 
     738           0 :   ulong nonce_ack = 0UL;
     739           0 :   if( FD_LIKELY( protobuf_sz ) ) {
     740           0 :     fd_pb_tlv_t event_id;
     741           0 :     if( FD_UNLIKELY( !fd_pb_read_tlv( inbuf, &event_id ) ||
     742           0 :                      event_id.field_id!=1U /* event_id */ ||
     743           0 :                      event_id.wire_type!=FD_PB_WIRE_TYPE_VARINT ) ) {
     744           0 :       FD_LOG_WARNING(( "Event gRPC rx msg: invalid Protobuf" ));
     745           0 :       client->defer_disconnect = DISCONNECT_REASON_INVALID_PROTOBUF;
     746           0 :       return;
     747           0 :     }
     748           0 :     nonce_ack = event_id.varint;
     749             : 
     750           0 :     if( FD_UNLIKELY( fd_pb_inbuf_sz( inbuf ) ) ) {
     751           0 :       FD_LOG_WARNING(( "Event gRPC rx msg: trailing data in StreamEventsResponse" ));
     752           0 :       client->defer_disconnect = DISCONNECT_REASON_INVALID_PROTOBUF;
     753           0 :       return;
     754           0 :     }
     755           0 :   }
     756             : 
     757           0 :   client->metrics.events_acked++;
     758           0 :   client->last_response_ns = client->now;
     759           0 :   if( FD_UNLIKELY( client->now-client->connected.connected_timestamp>=10L*(long)1e9 ) ) client->consecutive_failure_count = 0UL; /* responding and stayed up */
     760           0 :   if( FD_UNLIKELY( nonce_ack==ULONG_MAX ) ) return;
     761             : 
     762             :   /* Acks are cumulative over the circq, so the new stream's acks would
     763             :      also pop events still in flight on the draining stream. */
     764           0 :   ulong gen = request_ctx>>8;
     765           0 :   if( FD_UNLIKELY( client->drain_gen && gen!=client->drain_gen ) ) {
     766           0 :     client->held_ack = nonce_ack;
     767           0 :     return;
     768           0 :   }
     769           0 :   if( FD_UNLIKELY( !client->drain_gen && gen!=client->stream_gen ) ) return; /* stale watermark from a drained stream */
     770             : 
     771           0 :   ack_apply( client, nonce_ack );
     772           0 :   if( FD_UNLIKELY( client->drain_gen && nonce_ack>=client->drain_last ) ) drain_end( client );
     773           0 : }
     774             : 
     775             : void
     776             : fd_event_client_grpc_rx_msg( void *       app_ctx,
     777             :                              void const * protobuf,
     778             :                              ulong        protobuf_sz,
     779           0 :                              ulong        request_ctx ) {
     780           0 :   fd_event_client_t * client = app_ctx;
     781             : 
     782           0 :   switch( request_ctx & 0xffUL ) {
     783           0 :     case FD_EVENT_CLIENT_REQ_CTX_AUTHENTICATE:
     784           0 :       fd_event_client_handle_auth_challenge_resp( client, protobuf, protobuf_sz );
     785           0 :       break;
     786           0 :     case FD_EVENT_CLIENT_REQ_CTX_STREAM_EVENTS:
     787           0 :       fd_event_client_handle_stream_events_resp( client, protobuf, protobuf_sz, request_ctx );
     788           0 :       break;
     789           0 :     default:
     790           0 :       FD_LOG_WARNING(( "Unknown request_ctx: %lu, disconnecting", request_ctx ));
     791           0 :       client->defer_disconnect = DISCONNECT_REASON_INVALID_PROTOBUF;
     792           0 :       break;
     793           0 :   }
     794           0 : }
     795             : 
     796             : void
     797             : fd_event_client_grpc_rx_end( void *                app_ctx,
     798             :                              ulong                 request_ctx,
     799           0 :                              fd_grpc_resp_hdrs_t * resp ) {
     800           0 :   fd_event_client_t * client = app_ctx;
     801             : 
     802           0 :   if( FD_UNLIKELY( client->drain_stream && request_ctx==client->drain_stream->request_ctx ) ) {
     803           0 :     client->drain_stream = NULL; /* released by the gRPC client */
     804           0 :     if( FD_LIKELY( !client->drain_gen ) ) return;
     805             :     /* Unacked events on the old stream sit behind the cursor: resend
     806             :        them by reconnecting. */
     807           0 :     FD_LOG_WARNING(( "telemetry server ended rotated stream before acking it (%lu<%lu)", client->metrics.last_acked_id, client->drain_last ));
     808           0 :     client->defer_disconnect = DISCONNECT_REASON_PEER_CLOSED;
     809           0 :     return;
     810           0 :   }
     811             : 
     812           0 :   if( FD_UNLIKELY( resp->h2_status!=200 ) ) {
     813           0 :     FD_LOG_WARNING(( "telemetry server request failed %s(HTTP status %u)%s", fd_log_style_dim(), resp->h2_status, fd_log_style_normal() ));
     814           0 :     client->defer_disconnect = DISCONNECT_REASON_TRANSPORT_FAILED;
     815           0 :     return;
     816           0 :   }
     817             : 
     818           0 :   resp->grpc_msg_len = (uint)fd_url_unescape( resp->grpc_msg, resp->grpc_msg_len );
     819           0 :   if( !resp->grpc_msg_len ) {
     820           0 :     fd_memcpy( resp->grpc_msg, "unknown error", 13 );
     821           0 :     resp->grpc_msg_len = 13;
     822           0 :   }
     823             : 
     824           0 :   if( FD_UNLIKELY( resp->grpc_status!=FD_GRPC_STATUS_OK ) ) {
     825           0 :     switch( request_ctx & 0xffUL ) {
     826           0 :     case FD_EVENT_CLIENT_REQ_CTX_AUTHENTICATE:
     827           0 :       FD_LOG_WARNING(( "telemetry server authentication failed: %.*s %s(%u-%s)%s",
     828           0 :                        (int)resp->grpc_msg_len, resp->grpc_msg,
     829           0 :                        fd_log_style_dim(), resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ), fd_log_style_normal() ));
     830           0 :       client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     831           0 :       return;
     832           0 :     case FD_EVENT_CLIENT_REQ_CTX_STREAM_EVENTS:
     833           0 :       FD_LOG_WARNING(( "telemetry server event stream failed: %.*s %s(%u-%s)%s",
     834           0 :                        (int)resp->grpc_msg_len, resp->grpc_msg,
     835           0 :                        fd_log_style_dim(), resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ), fd_log_style_normal() ));
     836           0 :       client->defer_disconnect = DISCONNECT_REASON_PEER_CLOSED;
     837           0 :       return;
     838           0 :     default:
     839           0 :       FD_LOG_WARNING(( "telemetry server request failed: %.*s %s(%u-%s)%s",
     840           0 :                        (int)resp->grpc_msg_len, resp->grpc_msg,
     841           0 :                        fd_log_style_dim(), resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ), fd_log_style_normal() ));
     842           0 :       client->defer_disconnect = DISCONNECT_REASON_TRANSPORT_FAILED;
     843           0 :       return;
     844           0 :     }
     845           0 :   }
     846             : 
     847           0 :   if( (request_ctx & 0xffUL)==FD_EVENT_CLIENT_REQ_CTX_STREAM_EVENTS ) {
     848           0 :     FD_LOG_INFO(( "telemetry server event stream ended gracefully" ));
     849           0 :     client->defer_disconnect = DISCONNECT_REASON_PEER_CLOSED;
     850           0 :   }
     851             : 
     852           0 :   if( FD_UNLIKELY( (request_ctx & 0xffUL)==FD_EVENT_CLIENT_REQ_CTX_AUTHENTICATE &&
     853           0 :                    client->state==FD_EVENT_CLIENT_STATE_CONNECTED &&
     854           0 :                    !client->event_stream && !client->auth_bearer_len ) ) {
     855           0 :     FD_LOG_WARNING(( "telemetry server ended authentication without a challenge" ));
     856           0 :     client->defer_disconnect = DISCONNECT_REASON_AUTH_FAILED;
     857           0 :   }
     858           0 : }
     859             : 
     860             : void
     861             : fd_event_client_grpc_rx_timeout( void * app_ctx,
     862             :                                  ulong  request_ctx,
     863           0 :                                  int    deadline_kind FD_PARAM_UNUSED ) {
     864           0 :   fd_event_client_t * client = (fd_event_client_t *)app_ctx;
     865           0 :   if( FD_UNLIKELY( client->drain_stream && request_ctx==client->drain_stream->request_ctx ) ) {
     866           0 :     client->drain_stream = NULL; /* released by the gRPC client */
     867           0 :     if( FD_LIKELY( !client->drain_gen ) ) return;
     868           0 :   }
     869           0 :   FD_LOG_WARNING(( "Event gRPC rx timeout" ));
     870           0 :   client->defer_disconnect = DISCONNECT_REASON_TRANSPORT_FAILED;
     871           0 :   client->event_stream     = NULL;
     872           0 : }
     873             : 
     874             : static void
     875           0 : fd_event_client_grpc_ping_ack( void * app_ctx ) {
     876           0 :   (void)app_ctx;
     877           0 :   FD_LOG_WARNING(( "Event gRPC ping ack" ));
     878           0 : }
     879             : 
     880             : static long
     881             : pace_refill( fd_event_client_t * client,
     882           0 :              long                now ) {
     883           0 :   long ns_per_byte = (long)1e9/FD_EVENT_CLIENT_TX_RATE_BPS;
     884           0 :   long dt          = fd_long_max( now-client->tx_tokens_ns, 0L );
     885           0 :   long grant       = fd_long_min( dt/ns_per_byte, FD_EVENT_CLIENT_TX_BURST-client->tx_tokens );
     886           0 :   client->tx_tokens   += grant;
     887           0 :   client->tx_tokens_ns = client->tx_tokens==FD_EVENT_CLIENT_TX_BURST ? now : client->tx_tokens_ns+grant*ns_per_byte;
     888           0 :   return client->tx_tokens;
     889           0 : }
     890             : 
     891             : 
     892             : static void
     893             : zstd_emit( fd_event_client_t * client,
     894             :            long                now,
     895             :            int *               charge_busy,
     896           0 :            ZSTD_EndDirective   mode ) {
     897           0 :   ZSTD_inBuffer in = { .src = NULL, .size = 0UL, .pos = 0UL };
     898           0 :   ulong rem = ZSTD_compressStream2( client->zst, &client->zout, &in, mode );
     899           0 :   if( FD_UNLIKELY( ZSTD_isError( rem ) || rem ) ) FD_LOG_CRIT(( "ZSTD_compressStream2 failed: %s", ZSTD_isError( rem ) ? ZSTD_getErrorName( rem ) : "output full" ));
     900           0 :   if( mode==ZSTD_e_end ) client->zframe_open = 0;
     901           0 :   ulong sz = client->zout.pos;
     902           0 :   FD_TEST( fd_grpc_client_stream_send_msg1( client->grpc_client, client->event_stream, client->zout.dst, sz ) );
     903           0 :   client->tx_tokens          -= (long)sz;
     904           0 :   client->stream_tx_sz       += sizeof(fd_grpc_hdr_t)+sz;
     905           0 :   client->metrics.events_sent += client->zbatch_cnt;
     906           0 :   client->last_stream_send_ns = now;
     907           0 :   client->zout.pos   = 0UL;
     908           0 :   client->zbatch_raw = 0UL;
     909           0 :   client->zbatch_cnt = 0UL;
     910           0 :   *charge_busy = 1;
     911           0 : }
     912             : 
     913             : static void
     914             : rotate( fd_event_client_t * client,
     915             :         long                now,
     916           0 :         int *               charge_busy ) {
     917           0 :   if( FD_UNLIKELY( client->drain_gen || client->drain_stream ) ) return; /* previous rotation still draining */
     918           0 :   if( FD_UNLIKELY( client->zframe_open ) ) { zstd_emit( client, now, charge_busy, ZSTD_e_end ); return; } /* close once the epilogue is out */
     919           0 :   if( FD_UNLIKELY( !fd_grpc_client_stream_close( client->grpc_client, client->event_stream ) ) ) return;
     920           0 :   FD_LOG_INFO(( "rotating telemetry event stream after %lu bytes", client->stream_tx_sz ));
     921           0 :   fd_grpc_client_deadline_set( client->event_stream, FD_GRPC_DEADLINE_RX_END, now+FD_EVENT_CLIENT_STREAM_DRAIN_NANOS );
     922           0 :   client->drain_stream      = client->event_stream;
     923           0 :   client->event_stream      = NULL;
     924           0 :   client->auth_send_pending = 1;
     925           0 :   if( FD_LIKELY( fd_circq_unsent_cnt( client->circq )<client->circq->cnt ) ) { /* unacked events in flight */
     926           0 :     client->drain_gen  = client->stream_gen;
     927           0 :     client->drain_last = client->circq->cursor_seq-1UL;
     928           0 :   }
     929           0 :   *charge_busy = 1;
     930           0 : }
     931             : 
     932             : static void
     933             : tx_zstd( fd_event_client_t * client,
     934             :          long                now,
     935             :          long                tokens,
     936           0 :          int *               charge_busy ) {
     937           0 :   for(;;) {
     938           0 :     ulong next_sz;
     939           0 :     int   has_next = fd_circq_cursor_peek( client->circq, &next_sz );
     940             : 
     941           0 :     if( client->zbatch_cnt && ( !has_next || tokens<=0L ||
     942           0 :                                 client->zbatch_raw>=FD_EVENT_CLIENT_ZSTD_BATCH ) ) {
     943           0 :       if( FD_UNLIKELY( now-client->zbatch_ns>=FD_EVENT_CLIENT_ZSTD_FLUSH_NANOS || client->zbatch_raw>=FD_EVENT_CLIENT_ZSTD_BATCH ) ) zstd_emit( client, now, charge_busy, ZSTD_e_flush );
     944           0 :       return;
     945           0 :     }
     946             : 
     947           0 :     if( !has_next ) {
     948           0 :       if( FD_UNLIKELY( now-client->last_stream_send_ns>FD_EVENT_CLIENT_HEARTBEAT_NANOS ) ) {
     949           0 :         if( FD_UNLIKELY( client->stream_tx_sz+sizeof(fd_grpc_hdr_t)>client->stream_body_max ) ) { rotate( client, now, charge_busy ); return; }
     950           0 :         if( FD_LIKELY( fd_grpc_client_stream_send_msg1( client->grpc_client, client->event_stream, (uchar const *)"", 0UL ) ) ) {
     951           0 :           client->stream_tx_sz       += sizeof(fd_grpc_hdr_t);
     952           0 :           client->last_stream_send_ns = now;
     953           0 :           *charge_busy = 1;
     954           0 :         }
     955           0 :       }
     956           0 :       return;
     957           0 :     }
     958           0 :     if( FD_UNLIKELY( tokens<=0L ) ) return;
     959             : 
     960             :     /* Worst case batch size on the wire with this event */
     961           0 :     ulong raw = client->zbatch_raw + fd_pb_varint64_sz_max + next_sz;
     962           0 :     ulong out = FD_EVENT_CLIENT_ZSTD_OUT_MAX( raw );
     963           0 :     if( FD_UNLIKELY( client->stream_tx_sz+sizeof(fd_grpc_hdr_t)+out>client->stream_body_max ||
     964           0 :                      out>client->zout.size ) ) {
     965           0 :       if( client->zbatch_cnt ) { zstd_emit( client, now, charge_busy, ZSTD_e_flush ); return; }
     966           0 :       if( FD_UNLIKELY( out>client->zout.size ) ) FD_LOG_CRIT(( "%lu byte telemetry event does not fit the %lu byte gRPC buffer", next_sz, client->grpc_client->nanopb_tx_max ));
     967           0 :       rotate( client, now, charge_busy );
     968           0 :       return;
     969           0 :     }
     970             : 
     971           0 :     ulong msg_sz;
     972           0 :     uchar const * msg = fd_circq_cursor_advance( client->circq, &msg_sz );
     973           0 :     uchar prefix[ fd_pb_varint64_sz_max ];
     974           0 :     ulong prefix_sz = (ulong)( fd_pb_append_varint64( prefix, msg_sz )-prefix );
     975           0 :     ZSTD_inBuffer in0 = { .src = prefix, .size = prefix_sz, .pos = 0UL };
     976           0 :     ZSTD_inBuffer in1 = { .src = msg,    .size = msg_sz,    .pos = 0UL };
     977           0 :     ulong r0 = ZSTD_compressStream2( client->zst, &client->zout, &in0, ZSTD_e_continue );
     978           0 :     ulong r1 = ZSTD_compressStream2( client->zst, &client->zout, &in1, ZSTD_e_continue );
     979           0 :     if( FD_UNLIKELY( ZSTD_isError( r0 ) || ZSTD_isError( r1 ) || in0.pos!=in0.size || in1.pos!=in1.size ) ) FD_LOG_CRIT(( "ZSTD_compressStream2 failed" ));
     980           0 :     if( !client->zbatch_cnt ) client->zbatch_ns = now;
     981           0 :     client->zframe_open = 1;
     982           0 :     client->zbatch_raw += prefix_sz+msg_sz;
     983           0 :     client->zbatch_cnt++;
     984           0 :     *charge_busy = 1;
     985           0 :   }
     986           0 : }
     987             : 
     988             : static void
     989             : tx( fd_event_client_t * client,
     990             :     long                now,
     991           0 :     int *               charge_busy ) {
     992           0 :   FD_TEST( client->state==FD_EVENT_CLIENT_STATE_CONNECTED );
     993             : 
     994           0 :   long tokens = pace_refill( client, now );
     995             : 
     996             :   /* Fully acked, but the server has not ended it.  Cancel before
     997             :      anything that waits on a stream slot: it may hold the only one. */
     998           0 :   if( FD_UNLIKELY( client->drain_stream && !client->drain_gen ) ) {
     999           0 :     if( FD_UNLIKELY( !fd_grpc_client_stream_cancel( client->grpc_client, client->drain_stream ) ) ) return;
    1000           0 :     client->drain_stream = NULL;
    1001           0 :     *charge_busy = 1;
    1002           0 :     return;
    1003           0 :   }
    1004             : 
    1005           0 :   if( FD_UNLIKELY( client->event_stream && client->grpc_client->request_stream != NULL && client->grpc_client->request_stream!=client->event_stream ) ) return;
    1006             : 
    1007           0 :   if( FD_UNLIKELY( client->event_stream ) ) {
    1008           0 :     if( FD_UNLIKELY( fd_grpc_client_stream_send_is_blocked( client->grpc_client ) ) ) return;
    1009           0 :   } else {
    1010           0 :     if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( client->grpc_client ) ) ) return;
    1011           0 :   }
    1012             : 
    1013           0 :   if( FD_UNLIKELY( !client->event_stream ) ) {
    1014           0 :     if( FD_UNLIKELY( !client->auth_bearer_len ) ) return; /* rotation awaiting a fresh token */
    1015           0 :     client->event_stream = fd_grpc_client_request_start1(
    1016           0 :         client->grpc_client,
    1017           0 :         "/events.v1.EventService/StreamEventsZstd", strlen("/events.v1.EventService/StreamEventsZstd"),
    1018           0 :         FD_EVENT_CLIENT_REQ_CTX_STREAM_EVENTS | ((client->stream_gen+1UL)<<8),
    1019           0 :         NULL, 0UL, /* headers only; first message sent later */
    1020           0 :         client->auth_bearer, client->auth_bearer_len,
    1021           0 :         1 /* streaming */ );
    1022           0 :     if( FD_UNLIKELY( !client->event_stream ) ) return; /* transient; retry next poll */
    1023           0 :     client->stream_gen++;
    1024           0 :     client->stream_tx_sz    = 0UL;
    1025           0 :     client->auth_bearer_len = 0UL;
    1026           0 :     FD_TEST( !ZSTD_isError( ZSTD_CCtx_reset( client->zst, ZSTD_reset_session_only ) ) );
    1027           0 :     client->zout       = (ZSTD_outBuffer){ .dst = client->grpc_client->nanopb_tx+sizeof(fd_grpc_hdr_t), .size = client->grpc_client->nanopb_tx_max-sizeof(fd_grpc_hdr_t), .pos = 0UL };
    1028           0 :     client->zbatch_raw  = 0UL;
    1029           0 :     client->zbatch_cnt  = 0UL;
    1030           0 :     client->zframe_open = 0;
    1031           0 :     fd_grpc_client_deadline_set( client->event_stream, FD_GRPC_DEADLINE_HEADER, now+(long)10e9 /* 10s */ );
    1032           0 :     client->last_stream_send_ns = now;
    1033           0 :     client->last_response_ns    = now;
    1034           0 :     *charge_busy = 1;
    1035           0 :     return;
    1036           0 :   }
    1037             : 
    1038           0 :   tx_zstd( client, now, tokens, charge_busy );
    1039           0 : }
    1040             : 
    1041             : static void
    1042             : epoll_out( fd_event_client_t * client,
    1043           0 :            int                 want ) {
    1044           0 :   if( FD_UNLIKELY( client->sockfd<0 ) ) return;
    1045           0 :   if( FD_LIKELY( want==client->epoll_out_armed ) ) return;
    1046           0 :   struct epoll_event ev = { .events = EPOLLIN | (want ? EPOLLOUT : 0U), .data.fd = client->sockfd };
    1047           0 :   if( FD_UNLIKELY( -1==epoll_ctl( client->epoll_fd, EPOLL_CTL_MOD, client->sockfd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(MOD,sockfd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1048           0 :   client->epoll_out_armed = want;
    1049           0 : }
    1050             : 
    1051             : long
    1052             : fd_event_client_next_deadline( fd_event_client_t const * client,
    1053           0 :                                long                      now ) {
    1054           0 :   if( FD_UNLIKELY( !client->has_genesis_hash || !client->has_shred_version ) ) return LONG_MAX; /* frag-driven */
    1055             : 
    1056           0 :   switch( client->state ) {
    1057           0 :   case FD_EVENT_CLIENT_STATE_DISCONNECTED:
    1058           0 :     return fd_long_max( client->disconnected.reconnect_deadline, now );
    1059           0 :   case FD_EVENT_CLIENT_STATE_CONNECTING:
    1060             :     /* Completion/failure arrives as an EPOLLOUT/EPOLLERR event. */
    1061           0 :     return client->connecting.connect_deadline;
    1062           0 :   default: break;
    1063           0 :   }
    1064             : 
    1065             :   /* Decrypted TLS bytes buffered in the gRPC client do not make the fd
    1066             :      readable: drain now or they wait for the next unrelated event.
    1067             :      Except while TX is blocked: then they are held because the HTTP/2
    1068             :      rings are full behind the parked send, and only the EPOLLOUT armed
    1069             :      for that send can make progress. */
    1070           0 :   if( FD_UNLIKELY( client->use_tls &&
    1071           0 :                    fd_grpc_client_tls_rx_pending( client->grpc_client ) &&
    1072           0 :                    !fd_grpc_client_tls_tx_pending( client->grpc_client ) ) ) return now;
    1073             : 
    1074           0 :   long deadline = fd_grpc_client_next_deadline( client->grpc_client );
    1075           0 :   if( FD_UNLIKELY( client->state==FD_EVENT_CLIENT_STATE_AUTHENTICATING ) ) deadline = fd_long_min( deadline, client->auth_deadline );
    1076           0 :   if( FD_UNLIKELY( client->auth_send_pending && !fd_grpc_client_request_is_blocked( client->grpc_client ) ) ) deadline = now;
    1077           0 :   if( FD_LIKELY( client->state==FD_EVENT_CLIENT_STATE_CONNECTED && client->event_stream ) ) {
    1078           0 :     deadline = fd_long_min( deadline, fd_long_min( client->last_response_ns   +FD_EVENT_CLIENT_RESPONSE_TIMEOUT_NANOS,
    1079           0 :                                                    client->last_stream_send_ns+FD_EVENT_CLIENT_HEARTBEAT_NANOS ) );
    1080           0 :     if( FD_UNLIKELY( client->tx_tokens<=0L && fd_circq_unsent_cnt( client->circq ) ) ) {
    1081           0 :       deadline = fd_long_min( deadline, client->tx_tokens_ns + (1L-client->tx_tokens)*(long)1e9/FD_EVENT_CLIENT_TX_RATE_BPS );
    1082           0 :     }
    1083           0 :     if( FD_UNLIKELY( client->stall_since ) ) deadline = fd_long_min( deadline, client->stall_since+FD_EVENT_CLIENT_CREDIT_STALL_NANOS );
    1084           0 :     if( FD_UNLIKELY( client->zbatch_cnt ) ) deadline = fd_long_min( deadline, client->zbatch_ns+FD_EVENT_CLIENT_ZSTD_FLUSH_NANOS );
    1085           0 :   }
    1086           0 :   return deadline;
    1087           0 : }
    1088             : 
    1089             : static int
    1090             : credit_stall_check( fd_event_client_t * client,
    1091           0 :                     long                now ) {
    1092           0 :   ulong rem = fd_grpc_client_tx_starved( client->grpc_client );
    1093           0 :   if( FD_LIKELY( !rem ) ) {
    1094           0 :     client->stall_since = 0L;
    1095           0 :     return 0;
    1096           0 :   }
    1097           0 :   if( !client->stall_since || rem!=client->stall_rem || client->metrics.events_sent!=client->stall_events_sent ) {
    1098           0 :     client->stall_since       = now;
    1099           0 :     client->stall_rem         = rem;
    1100           0 :     client->stall_events_sent = client->metrics.events_sent;
    1101           0 :     return 0;
    1102           0 :   }
    1103           0 :   if( FD_LIKELY( now-client->stall_since<=FD_EVENT_CLIENT_CREDIT_STALL_NANOS ) ) return 0;
    1104           0 :   FD_LOG_WARNING(( "telemetry server stopped granting flow-control credit for over %ld seconds, reconnecting", FD_EVENT_CLIENT_CREDIT_STALL_NANOS/(long)1e9 ));
    1105           0 :   client->metrics.credit_stall_cnt++;
    1106           0 :   disconnect( client, now, DISCONNECT_REASON_TIMEOUT, 0, 1 );
    1107           0 :   return 1;
    1108           0 : }
    1109             : 
    1110             : static void
    1111             : poll1( fd_event_client_t * client,
    1112             :        long                now,
    1113           0 :        int *               charge_busy ) {
    1114           0 :   if( FD_UNLIKELY( !client->has_genesis_hash || !client->has_shred_version ) ) return;
    1115             : 
    1116           0 :   if( FD_UNLIKELY( client->state==FD_EVENT_CLIENT_STATE_DISCONNECTED ) ) reconnect( client, now, charge_busy );
    1117           0 :   if( FD_UNLIKELY( client->state==FD_EVENT_CLIENT_STATE_CONNECTING ) ) {
    1118           0 :     if( FD_UNLIKELY( now>client->connecting.connect_deadline ) ) {
    1119           0 :       disconnect( client, now, DISCONNECT_REASON_TIMEOUT, 0, 1 );
    1120           0 :       return;
    1121           0 :     }
    1122           0 :   }
    1123             :   /* Check auth handshake timeout */
    1124           0 :   if( FD_UNLIKELY( client->state==FD_EVENT_CLIENT_STATE_AUTHENTICATING && now>client->auth_deadline ) ) {
    1125           0 :     FD_LOG_WARNING(( "auth handshake timed out" ));
    1126           0 :     client->metrics.handshake_timeout_cnt++;
    1127           0 :     disconnect( client, now, DISCONNECT_REASON_TIMEOUT, 0, 1 );
    1128           0 :     return;
    1129           0 :   }
    1130           0 :   if( FD_LIKELY( client->state!=FD_EVENT_CLIENT_STATE_DISCONNECTED ) ) {
    1131           0 :     int rxtx_err;
    1132           0 :     if( client->use_tls )
    1133           0 :       rxtx_err = fd_grpc_client_rxtx_tls( client->grpc_client, client->tls_conn, client->sockfd, now, charge_busy );
    1134           0 :     else
    1135           0 :       rxtx_err = fd_grpc_client_rxtx_socket( client->grpc_client, client->sockfd, now, charge_busy );
    1136           0 :     if( FD_UNLIKELY( -1==rxtx_err ) ) {
    1137           0 :       disconnect( client, now, DISCONNECT_REASON_TRANSPORT_FAILED, errno, 1 );
    1138           0 :       return;
    1139           0 :     }
    1140           0 :   }
    1141             : 
    1142           0 :   if( FD_UNLIKELY( client->defer_disconnect!=INT_MAX ) ) {
    1143           0 :     int reason = client->defer_disconnect;
    1144           0 :     if( reason==DISCONNECT_REASON_AUTH_FAILED ) client->metrics.auth_fail_cnt++;
    1145           0 :     if( reason==DISCONNECT_REASON_INVALID_PROTOBUF ) client->metrics.invalid_msg_cnt++;
    1146           0 :     disconnect( client, now, reason, 0, 1 );
    1147           0 :     return;
    1148           0 :   }
    1149             : 
    1150           0 :   if( FD_UNLIKELY( client->auth_send_pending ) ) fd_event_client_try_send_authenticate( client, now );
    1151             : 
    1152           0 :   if( FD_LIKELY( client->state==FD_EVENT_CLIENT_STATE_CONNECTED ) ) {
    1153           0 :     if( FD_UNLIKELY( client->event_stream && now-client->last_response_ns>FD_EVENT_CLIENT_RESPONSE_TIMEOUT_NANOS ) ) {
    1154           0 :       FD_LOG_WARNING(( "no response from telemetry server in over %ld seconds", FD_EVENT_CLIENT_RESPONSE_TIMEOUT_NANOS/(long)1e9 ));
    1155           0 :       disconnect( client, now, DISCONNECT_REASON_TIMEOUT, 0, 1 );
    1156           0 :       return;
    1157           0 :     }
    1158           0 :     if( FD_UNLIKELY( credit_stall_check( client, now ) ) ) return;
    1159           0 :     tx( client, now, charge_busy );
    1160           0 :   }
    1161           0 : }
    1162             : 
    1163             : void
    1164             : fd_event_client_poll( fd_event_client_t * client,
    1165             :                       long                now,
    1166           0 :                       int *               charge_busy ) {
    1167           0 :   client->now = now;
    1168           0 :   poll1( client, now, charge_busy );
    1169             : 
    1170             :   /* poll1 flushes before tx() enqueues: flush once more so bytes queued
    1171             :      this poll go out now instead of arming EPOLLOUT on a writable
    1172             :      socket (a spurious waker roundtrip per message). */
    1173           0 :   if( FD_UNLIKELY( client->state!=FD_EVENT_CLIENT_STATE_DISCONNECTED && fd_grpc_client_tx_pending( client->grpc_client ) ) ) {
    1174           0 :     int flush_err = client->use_tls ? fd_grpc_client_tls_flush     ( client->grpc_client, client->sockfd )
    1175           0 :                                     : fd_grpc_client_tx_flush_socket( client->grpc_client, client->sockfd );
    1176           0 :     if( FD_UNLIKELY( -1==flush_err ) ) {
    1177           0 :       disconnect( client, now, DISCONNECT_REASON_TRANSPORT_FAILED, errno, 1 );
    1178           0 :       return;
    1179           0 :     }
    1180           0 :   }
    1181             : 
    1182             :   /* Arm EPOLLOUT only on write demand: unsent HTTP/2 bytes, or a TLS
    1183             :      handshake blocked on write (which is also what an in-progress
    1184             :      TCP connect looks like: the handshake write EAGAINed, and connect
    1185             :      completion is the writability event).  Anything else leaves an
    1186             :      idle-writable socket keeping the epoll set ready. */
    1187           0 :   int want = fd_grpc_client_tx_pending( client->grpc_client );
    1188           0 :   want |= client->use_tls && fd_grpc_client_tls_tx_pending( client->grpc_client );
    1189           0 :   epoll_out( client, want );
    1190           0 : }
    1191             : 
    1192             : fd_grpc_client_callbacks_t fd_event_client_grpc_callbacks = {
    1193             :   .conn_established = fd_event_client_grpc_conn_established,
    1194             :   .conn_dead        = fd_event_client_grpc_conn_dead,
    1195             :   .tx_complete      = fd_event_client_grpc_tx_complete,
    1196             :   .rx_start         = fd_event_client_grpc_rx_start,
    1197             :   .rx_msg           = fd_event_client_grpc_rx_msg,
    1198             :   .rx_end           = fd_event_client_grpc_rx_end,
    1199             :   .rx_timeout       = fd_event_client_grpc_rx_timeout,
    1200             :   .ping_ack         = fd_event_client_grpc_ping_ack,
    1201             : };

Generated by: LCOV version 1.14