LCOV - code coverage report
Current view: top level - disco/bundle - fd_bundle_client.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 822 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 39 0.0 %

          Line data    Source code
       1             : /* fd_bundle_client.c steps gRPC related tasks. */
       2             : 
       3             : #define _GNU_SOURCE /* SOL_TCP */
       4             : #include "fd_bundle_auth.h"
       5             : #include "fd_bundle_tile_private.h"
       6             : #include "fd_bundle_tile.h"
       7             : #include "../waker/fd_waker.h"
       8             : #include "../fd_txn_m.h"
       9             : #include "../../waltz/h2/fd_h2_conn.h"
      10             : #include "../../waltz/http/fd_url.h" /* fd_url_unescape */
      11             : #include "../../ballet/base58/fd_base58.h"
      12             : #include "../../ballet/ed25519/fd_x25519.h"
      13             : #include "../../ballet/pb/fd_pb_tokenize.h"
      14             : #include "../../util/net/fd_ip4.h"
      15             : 
      16             : #include <fcntl.h>
      17             : #include <errno.h>
      18             : #include <unistd.h> /* close */
      19             : #include <poll.h> /* poll */
      20             : #include <sys/epoll.h>
      21             : #include <sys/socket.h> /* socket */
      22             : #include <netinet/in.h>
      23             : #include <netinet/ip.h>
      24             : #include <netinet/tcp.h>
      25             : 
      26           0 : #define FD_BUNDLE_CLIENT_REQUEST_TIMEOUT ((long)8e9) /* 8 seconds */
      27           0 : #define FD_BUNDLE_CONN_TIMEOUT_NS        ((long)3e9) /* TCP connect plus TLS and HTTP/2 setup */
      28             : 
      29             : 
      30             : __attribute__((weak)) long
      31           0 : fd_bundle_now( fd_bundle_tile_t const * ctx ) {
      32           0 :   return fd_clock_tile_now( ctx->clock );
      33           0 : }
      34             : 
      35             : void
      36           0 : fd_bundle_client_reset( fd_bundle_tile_t * ctx ) {
      37           0 :   if( FD_UNLIKELY( ctx->tcp_sock >= 0 ) ) {
      38           0 :     if( FD_UNLIKELY( 0!=close( ctx->tcp_sock ) ) ) {
      39           0 :       FD_LOG_ERR(( "close(tcp_sock=%i) failed (%i-%s)", ctx->tcp_sock, errno, fd_io_strerror( errno ) ));
      40           0 :     }
      41           0 :     ctx->tcp_sock = -1;
      42           0 :     ctx->tcp_sock_connected = 0;
      43           0 :     ctx->sock_in_epoll = 0;
      44           0 :     ctx->epoll_out_armed = 0;
      45           0 :   }
      46           0 :   fd_tlsrec_conn_fini( ctx->tls_conn );
      47           0 :   fd_memzero_explicit( ctx->tls->kex_private_key, 32UL );
      48           0 :   ctx->defer_reset = 0;
      49             : 
      50           0 :   ctx->builder_info_avail       = 0;
      51           0 :   ctx->builder_info_wait        = 0;
      52           0 :   ctx->packet_subscription_live = 0;
      53           0 :   ctx->packet_subscription_wait = 0;
      54           0 :   ctx->bundle_subscription_live = 0;
      55           0 :   ctx->bundle_subscription_wait = 0;
      56             : 
      57           0 :   fd_memset( ctx->rtt, 0, sizeof(fd_rtt_estimate_t) );
      58             : 
      59           0 :   fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
      60             : 
      61           0 :   fd_bundle_auther_reset( &ctx->auther );
      62           0 :   fd_grpc_client_reset( ctx->grpc_client );
      63           0 : }
      64             : 
      65             : static int
      66             : fd_bundle_client_do_connect( fd_bundle_tile_t const * ctx,
      67           0 :                              uint                     ip4_addr ) {
      68           0 :   struct sockaddr_in addr = {
      69           0 :     .sin_family      = AF_INET,
      70           0 :     .sin_addr.s_addr = ip4_addr,
      71           0 :     .sin_port        = fd_ushort_bswap( ctx->server_tcp_port )
      72           0 :   };
      73           0 :   int err = connect( ctx->tcp_sock, fd_type_pun_const( &addr ), sizeof(struct sockaddr_in) );
      74             :   /* FD_LIKELY is used here as EINPROGRESS is expected even to local tcp ports */
      75           0 :   if( FD_LIKELY( err==-1 ) ) {
      76           0 :     return errno;
      77           0 :   }
      78           0 :   return 0;
      79           0 : }
      80             : 
      81             : static int
      82           0 : fd_bundle_client_get_connect_result( fd_bundle_tile_t const * ctx ) {
      83           0 :   int so_err = 0;
      84           0 :   socklen_t so_err_sz = sizeof(so_err);
      85           0 :   if( FD_UNLIKELY( getsockopt( ctx->tcp_sock, SOL_SOCKET, SO_ERROR, &so_err, &so_err_sz )==-1 ) ) {
      86           0 :     return errno;
      87           0 :   }
      88           0 :   return so_err;
      89           0 : }
      90             : 
      91             : static void
      92           0 : fd_bundle_client_create_conn( fd_bundle_tile_t * ctx ) {
      93           0 :   fd_bundle_client_reset( ctx );
      94             : 
      95             :   /* FIXME IPv6 support */
      96           0 :   fd_addrinfo_t hints = {0};
      97           0 :   hints.ai_family = AF_INET;
      98           0 :   fd_addrinfo_t * res = NULL;
      99           0 :   uchar scratch[ 4096 ];
     100           0 :   void * pscratch = scratch;
     101           0 :   int err = fd_getaddrinfo( ctx->server_fqdn, &hints, &res, &pscratch, sizeof(scratch) );
     102           0 :   if( FD_UNLIKELY( err ) ) {
     103           0 :     FD_LOG_WARNING(( "fd_getaddrinfo `%s` failed (%d-%s)", ctx->server_fqdn, err, fd_gai_strerror( err ) ));
     104           0 :     fd_bundle_client_reset( ctx );
     105           0 :     ctx->metrics.transport_fail_cnt++;
     106           0 :     return;
     107           0 :   }
     108           0 :   uint const ip4_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr;
     109           0 :   ctx->server_ip4_addr = ip4_addr;
     110             : 
     111           0 :   int tcp_sock = socket( AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0 );
     112           0 :   if( FD_UNLIKELY( tcp_sock<0 ) ) {
     113           0 :     FD_LOG_ERR(( "socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     114           0 :   }
     115           0 :   ctx->tcp_sock = tcp_sock;
     116             : 
     117           0 :   struct epoll_event ev = { .events = EPOLLIN|EPOLLOUT, .data.fd = tcp_sock };
     118           0 :   if( FD_UNLIKELY( -1==epoll_ctl( FD_WAKER_INNER_FD( ctx->waker_client_idx ), EPOLL_CTL_ADD, tcp_sock, &ev ) ) ) {
     119           0 :     FD_LOG_ERR(( "epoll_ctl(ADD,tcp_sock) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     120           0 :   }
     121           0 :   ctx->sock_in_epoll   = 1;
     122           0 :   ctx->epoll_out_armed = 1;
     123             : 
     124           0 :   if( FD_UNLIKELY( 0!=setsockopt( tcp_sock, SOL_SOCKET, SO_RCVBUF, &ctx->so_rcvbuf, sizeof(int) ) ) ) {
     125           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_RCVBUF,%i) failed (%i-%s)", ctx->so_rcvbuf, errno, fd_io_strerror( errno ) ));
     126           0 :   }
     127             : 
     128           0 :   int tcp_nodelay = 1;
     129           0 :   if( FD_UNLIKELY( 0!=setsockopt( tcp_sock, SOL_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int) ) ) ) {
     130           0 :     FD_LOG_ERR(( "setsockopt failed (%d-%s)", errno, fd_io_strerror( errno ) ));
     131           0 :   }
     132             : 
     133           0 :   if( FD_UNLIKELY( fcntl( tcp_sock, F_SETFL, O_NONBLOCK )==-1 ) ) {
     134           0 :     FD_LOG_ERR(( "fcntl(tcp_sock,F_SETFL,O_NONBLOCK) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     135           0 :   }
     136             : 
     137           0 :   char const * scheme = ctx->is_ssl ? "https" : "http";
     138             : 
     139           0 :   FD_LOG_INFO(( "Connecting to %s://" FD_IP4_ADDR_FMT ":%hu (%.*s)",
     140           0 :                 scheme,
     141           0 :                 FD_IP4_ADDR_FMT_ARGS( ip4_addr ), ctx->server_tcp_port,
     142           0 :                 (int)ctx->server_sni_len, ctx->server_sni ));
     143             : 
     144           0 :   int connect_err = fd_bundle_client_do_connect( ctx, ip4_addr );
     145             :   /* FD_LIKELY as EINPROGRESS is expected */
     146           0 :   if( FD_LIKELY( connect_err ) ) {
     147           0 :     if( FD_UNLIKELY( connect_err!=EINPROGRESS ) ) {
     148           0 :       FD_LOG_WARNING(( "connect(tcp_sock," FD_IP4_ADDR_FMT ":%u) failed (%i-%s)",
     149           0 :                       FD_IP4_ADDR_FMT_ARGS( ip4_addr ), ctx->server_tcp_port,
     150           0 :                       connect_err, fd_io_strerror( connect_err ) ));
     151           0 :       fd_bundle_client_reset( ctx );
     152           0 :       ctx->metrics.transport_fail_cnt++;
     153           0 :       return;
     154           0 :     }
     155           0 :   }
     156             : 
     157           0 :   if( ctx->is_ssl ) {
     158           0 :     fd_tls_t * tls = ctx->tls;
     159           0 :     ulong sni_len = strlen( ctx->server_sni );
     160           0 :     fd_memcpy( tls->server_name, ctx->server_sni, sni_len );
     161           0 :     tls->server_name[ sni_len ] = '\0';
     162           0 :     tls->server_name_len = (ushort)sni_len;
     163           0 :     if( FD_UNLIKELY( !fd_rng_secure( tls->kex_private_key, 32UL ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
     164           0 :     fd_x25519_public( tls->kex_public_key, tls->kex_private_key );
     165           0 :     fd_tlsrec_conn_init( ctx->tls_conn, tls, 0 );
     166           0 :   }
     167             : 
     168           0 :   fd_grpc_client_reset( ctx->grpc_client );
     169           0 :   fd_keepalive_init( ctx->keepalive, ctx->rng, ctx->keepalive_interval, ctx->keepalive_interval, fd_bundle_now( ctx ) );
     170           0 :   ctx->conn_deadline = fd_bundle_now( ctx ) + FD_BUNDLE_CONN_TIMEOUT_NS;
     171           0 : }
     172             : 
     173             : static int
     174             : fd_bundle_client_drive_io( fd_bundle_tile_t * ctx,
     175             :                            long               now,
     176           0 :                            int *              charge_busy ) {
     177           0 :   if( ctx->is_ssl ) {
     178           0 :     return fd_grpc_client_rxtx_tls( ctx->grpc_client, ctx->tls_conn, ctx->tcp_sock, now, charge_busy );
     179           0 :   }
     180             : 
     181           0 :   return fd_grpc_client_rxtx_socket( ctx->grpc_client, ctx->tcp_sock, now, charge_busy );
     182           0 : }
     183             : 
     184             : static void
     185           0 : fd_bundle_client_request_builder_info( fd_bundle_tile_t * ctx ) {
     186           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( ctx->grpc_client ) ) ) return;
     187             : 
     188           0 :   static char const path[] = "/block_engine.BlockEngineValidator/GetBlockBuilderFeeInfo";
     189           0 :   fd_grpc_h2_stream_t * request = fd_grpc_client_request_start(
     190           0 :       ctx->grpc_client,
     191           0 :       path, sizeof(path)-1,
     192           0 :       FD_BUNDLE_CLIENT_REQ_Bundle_GetBlockBuilderFeeInfo,
     193           0 :       (uchar const *)"", 0UL, /* empty request message */
     194           0 :       ctx->auther.access_token, ctx->auther.access_token_sz,
     195           0 :       0 /* is_streaming */
     196           0 :   );
     197           0 :   if( FD_UNLIKELY( !request ) ) return;
     198           0 :   fd_grpc_client_deadline_set(
     199           0 :       request,
     200           0 :       FD_GRPC_DEADLINE_RX_END,
     201           0 :       fd_log_wallclock() + FD_BUNDLE_CLIENT_REQUEST_TIMEOUT );
     202             : 
     203           0 :   ctx->builder_info_wait = 1;
     204           0 : }
     205             : 
     206             : static void
     207           0 : fd_bundle_client_subscribe_packets( fd_bundle_tile_t * ctx ) {
     208           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( ctx->grpc_client ) ) ) return;
     209             : 
     210           0 :   static char const path[] = "/block_engine.BlockEngineValidator/SubscribePackets";
     211           0 :   fd_grpc_h2_stream_t * request = fd_grpc_client_request_start(
     212           0 :       ctx->grpc_client,
     213           0 :       path, sizeof(path)-1,
     214           0 :       FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets,
     215           0 :       (uchar const *)"", 0UL, /* empty request message */
     216           0 :       ctx->auther.access_token, ctx->auther.access_token_sz,
     217           0 :       0 /* is_streaming */
     218           0 :   );
     219           0 :   if( FD_UNLIKELY( !request ) ) return;
     220           0 :   fd_grpc_client_deadline_set(
     221           0 :       request,
     222           0 :       FD_GRPC_DEADLINE_HEADER,
     223           0 :       fd_log_wallclock() + FD_BUNDLE_CLIENT_REQUEST_TIMEOUT );
     224             : 
     225           0 :   ctx->packet_subscription_wait = 1;
     226           0 : }
     227             : 
     228             : static void
     229           0 : fd_bundle_client_subscribe_bundles( fd_bundle_tile_t * ctx ) {
     230           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( ctx->grpc_client ) ) ) return;
     231             : 
     232           0 :   static char const path[] = "/block_engine.BlockEngineValidator/SubscribeBundles";
     233           0 :   fd_grpc_h2_stream_t * request = fd_grpc_client_request_start(
     234           0 :       ctx->grpc_client,
     235           0 :       path, sizeof(path)-1,
     236           0 :       FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles,
     237           0 :       (uchar const *)"", 0UL, /* empty request message */
     238           0 :       ctx->auther.access_token, ctx->auther.access_token_sz,
     239           0 :       0 /* is_streaming */
     240           0 :   );
     241           0 :   if( FD_UNLIKELY( !request ) ) return;
     242           0 :   fd_grpc_client_deadline_set(
     243           0 :       request,
     244           0 :       FD_GRPC_DEADLINE_HEADER,
     245           0 :       fd_log_wallclock() + FD_BUNDLE_CLIENT_REQUEST_TIMEOUT );
     246             : 
     247           0 :   ctx->bundle_subscription_wait = 1;
     248           0 : }
     249             : 
     250             : void
     251           0 : fd_bundle_client_send_ping( fd_bundle_tile_t * ctx ) {
     252           0 :   if( FD_UNLIKELY( !ctx->grpc_client ) ) return; /* no client */
     253           0 :   fd_h2_conn_t * conn = fd_grpc_client_h2_conn( ctx->grpc_client );
     254           0 :   if( FD_UNLIKELY( !conn ) ) return; /* no conn */
     255           0 :   if( FD_UNLIKELY( conn->flags ) ) return; /* conn busy */
     256           0 :   fd_h2_rbuf_t * rbuf_tx = fd_grpc_client_rbuf_tx( ctx->grpc_client );
     257             : 
     258           0 :   if( FD_LIKELY( fd_h2_tx_ping( conn, rbuf_tx ) ) ) {
     259           0 :     long now = fd_bundle_now( ctx );
     260           0 :     fd_keepalive_tx( ctx->keepalive, ctx->rng, now );
     261           0 :     FD_LOG_DEBUG(( "Keepalive TX (deadline=+%gs)", (double)( ctx->keepalive->ts_deadline-now )/1e9 ));
     262           0 :   }
     263           0 : }
     264             : 
     265             : static void
     266             : fd_bundle_client_epoll_out( fd_bundle_tile_t * ctx,
     267           0 :                             int                want ) {
     268           0 :   if( FD_UNLIKELY( !ctx->sock_in_epoll ) ) return;
     269           0 :   if( FD_LIKELY( want==(int)ctx->epoll_out_armed ) ) return;
     270           0 :   struct epoll_event ev = { .events = EPOLLIN | (want ? EPOLLOUT : 0U), .data.fd = ctx->tcp_sock };
     271           0 :   if( FD_UNLIKELY( -1==epoll_ctl( FD_WAKER_INNER_FD( ctx->waker_client_idx ), EPOLL_CTL_MOD, ctx->tcp_sock, &ev ) ) )
     272           0 :     FD_LOG_ERR(( "epoll_ctl(MOD,tcp_sock) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     273           0 :   ctx->epoll_out_armed = !!want;
     274           0 : }
     275             : 
     276             : long
     277             : fd_bundle_client_next_deadline( fd_bundle_tile_t const * ctx,
     278           0 :                                 long                     now ) {
     279             :   /* Connecting: completion/failure arrives as an EPOLLOUT/EPOLLERR
     280             :      event, or the setup times out. */
     281           0 :   if( FD_UNLIKELY( ctx->tcp_sock>=0 && !ctx->tcp_sock_connected ) ) return ctx->conn_deadline;
     282             : 
     283             :   /* Disconnected: next action is the reconnect attempt. */
     284           0 :   if( FD_UNLIKELY( ctx->tcp_sock<0 ) ) return fd_long_max( ctx->backoff_until, now );
     285             : 
     286             :   /* Decrypted TLS bytes buffered in the gRPC client do not make the fd
     287             :      readable: drain now or they wait for the next unrelated event.
     288             :      Except while TX is blocked: then they are held because the HTTP/2
     289             :      rings are full behind the parked send, and only the EPOLLOUT armed
     290             :      for that send can make progress. */
     291           0 :   if( FD_UNLIKELY( ctx->is_ssl &&
     292           0 :                    fd_grpc_client_tls_rx_pending( ctx->grpc_client ) &&
     293           0 :                    !fd_grpc_client_tls_tx_pending( ctx->grpc_client ) ) ) return now;
     294             : 
     295           0 :   long deadline = fd_grpc_client_next_deadline( ctx->grpc_client );
     296           0 :   if( FD_LIKELY( ctx->keepalive->interval ) )
     297           0 :     deadline = fd_long_min( deadline, ctx->keepalive->inflight ? ctx->keepalive->ts_deadline
     298           0 :                                                                : ctx->keepalive->ts_next_tx );
     299           0 :   if( FD_LIKELY( ctx->builder_info_avail & !ctx->builder_info_wait ) )
     300           0 :     deadline = fd_long_min( deadline, ctx->builder_info_valid_until );
     301           0 :   if( FD_UNLIKELY( ctx->backoff_until>now ) )
     302           0 :     deadline = fd_long_min( deadline, ctx->backoff_until );
     303           0 :   if( FD_UNLIKELY( !fd_grpc_client_is_connected( ctx->grpc_client ) ) )
     304           0 :     deadline = fd_long_min( deadline, ctx->conn_deadline );
     305           0 :   return deadline;
     306           0 : }
     307             : 
     308             : int
     309             : fd_bundle_client_step_reconnect( fd_bundle_tile_t * ctx,
     310           0 :                                  long               now ) {
     311             :   /* Drive auth */
     312           0 :   if( FD_UNLIKELY( ctx->auther.needs_poll ) ) {
     313           0 :     fd_bundle_auther_poll( &ctx->auther, ctx->grpc_client, ctx->keyguard_client );
     314           0 :     return 1;
     315           0 :   }
     316           0 :   if( FD_UNLIKELY( ctx->auther.state!=FD_BUNDLE_AUTH_STATE_DONE_WAIT ) ) return 0;
     317             : 
     318             :   /* Request block builder info */
     319           0 :   int const builder_info_expired = ( ctx->builder_info_valid_until - now )<0;
     320           0 :   if( FD_UNLIKELY( ( ( !ctx->builder_info_avail ) |
     321           0 :                      ( builder_info_expired     ) ) &
     322           0 :                    ( !ctx->builder_info_wait      ) ) ) {
     323           0 :     fd_bundle_client_request_builder_info( ctx );
     324           0 :     return 1;
     325           0 :   }
     326             : 
     327             :   /* Subscribe to packets */
     328           0 :   if( FD_UNLIKELY( !ctx->packet_subscription_live && !ctx->packet_subscription_wait ) ) {
     329           0 :     fd_bundle_client_subscribe_packets( ctx );
     330           0 :     return 1;
     331           0 :   }
     332             : 
     333             :   /* Subscribe to bundles */
     334           0 :   if( FD_UNLIKELY( !ctx->bundle_subscription_live && !ctx->bundle_subscription_wait ) ) {
     335           0 :     fd_bundle_client_subscribe_bundles( ctx );
     336           0 :     return 1;
     337           0 :   }
     338             : 
     339             :   /* Send a PING */
     340           0 :   if( FD_UNLIKELY( fd_keepalive_should_tx( ctx->keepalive, now ) ) ) {
     341           0 :     fd_bundle_client_send_ping( ctx );
     342           0 :     return 1;
     343           0 :   }
     344             : 
     345           0 :   return 0;
     346           0 : }
     347             : 
     348             : static void
     349             : fd_bundle_client_step1( fd_bundle_tile_t * ctx,
     350           0 :                         int *              charge_busy ) {
     351           0 :   if( FD_UNLIKELY( ctx->tcp_sock>=0 &&
     352           0 :                    !( ctx->tcp_sock_connected && fd_grpc_client_is_connected( ctx->grpc_client ) ) &&
     353           0 :                    fd_bundle_now( ctx )>=ctx->conn_deadline ) ) {
     354           0 :     FD_LOG_INFO(( "Bundle connect to " FD_IP4_ADDR_FMT ":%hu timed out (%s)", FD_IP4_ADDR_FMT_ARGS( ctx->server_ip4_addr ), ctx->server_tcp_port,
     355           0 :                   ctx->tcp_sock_connected ? "TLS/HTTP/2 setup" : "TCP connect" ));
     356           0 :     fd_bundle_client_reset( ctx );
     357           0 :     ctx->metrics.transport_fail_cnt++;
     358           0 :     *charge_busy = 1;
     359           0 :     return;
     360           0 :   }
     361             : 
     362             :   /* Wait for TCP socket to connect */
     363           0 :   if( FD_UNLIKELY( !ctx->tcp_sock_connected ) ) {
     364           0 :     if( FD_UNLIKELY( ctx->tcp_sock < 0 ) ) goto reconnect;
     365             : 
     366           0 :     struct pollfd pfds[1] = {
     367           0 :       { .fd = ctx->tcp_sock, .events = POLLOUT }
     368           0 :     };
     369           0 :     int poll_res = fd_syscall_poll( pfds, 1, 0 );
     370           0 :     if( FD_UNLIKELY( poll_res<0 ) ) {
     371           0 :       FD_LOG_ERR(( "fd_syscall_poll(tcp_sock) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     372           0 :     }
     373           0 :     if( poll_res==0 ) return;
     374             : 
     375           0 :     int connect_result = 0;
     376           0 :     if( pfds[0].revents & (POLLERR|POLLHUP) ) {
     377           0 :       connect_result = fd_bundle_client_get_connect_result( ctx );
     378           0 :     connect_failed:
     379           0 :       FD_LOG_INFO(( "Bundle gRPC connect attempt failed (%i-%s)", connect_result, fd_io_strerror( connect_result ) ));
     380           0 :       fd_bundle_client_reset( ctx );
     381           0 :       ctx->metrics.transport_fail_cnt++;
     382           0 :       *charge_busy = 1;
     383           0 :       return;
     384           0 :     }
     385           0 :     if( FD_UNLIKELY( !(pfds[0].revents & POLLOUT) ) ) return;
     386           0 :     connect_result = fd_bundle_client_get_connect_result( ctx );
     387           0 :     if( FD_UNLIKELY( connect_result!=0 ) ) {
     388           0 :       goto connect_failed;
     389           0 :     }
     390           0 :     FD_LOG_DEBUG(( "Bundle TCP socket connected" ));
     391           0 :     ctx->tcp_sock_connected = 1;
     392           0 :     fd_bundle_client_epoll_out( ctx, 0 );
     393           0 :     *charge_busy = 1;
     394           0 :   }
     395             : 
     396             :   /* gRPC conn died? */
     397           0 :   if( FD_UNLIKELY( !ctx->grpc_client ) ) {
     398           0 :     long sleep_start;
     399           0 :   reconnect:
     400           0 :     sleep_start = fd_bundle_now( ctx );
     401           0 :     if( FD_UNLIKELY( fd_bundle_tile_should_stall( ctx, sleep_start ) ) ) {
     402           0 :       long wait_dur = ctx->backoff_until - sleep_start;
     403           0 :       fd_log_sleep( fd_long_min( wait_dur, 1e6 ) );
     404           0 :       return;
     405           0 :     }
     406           0 :     fd_bundle_client_create_conn( ctx );
     407           0 :     *charge_busy = 1;
     408           0 :     return;
     409           0 :   }
     410             : 
     411             :   /* Did a HTTP/2 PING time out */
     412           0 :   long check_ts = ctx->cached_ts = fd_bundle_now( ctx );
     413           0 :   if( FD_UNLIKELY( fd_keepalive_is_timeout( ctx->keepalive, check_ts ) ) ) {
     414           0 :     FD_LOG_WARNING(( "Bundle gRPC timed out (HTTP/2 PING went unanswered for %.2f seconds)",
     415           0 :                      (double)( check_ts - ctx->keepalive->ts_last_tx )/1e9 ));
     416           0 :     ctx->keepalive->inflight = 0;
     417           0 :     ctx->defer_reset = 1;
     418           0 :     *charge_busy = 1;
     419           0 :     return;
     420           0 :   }
     421             : 
     422             :   /* Drive I/O, SSL handshake, and any inflight requests */
     423           0 :   if( FD_UNLIKELY( -1==fd_bundle_client_drive_io( ctx, check_ts, charge_busy ) || ctx->defer_reset /* new error? */ ) ) {
     424           0 :     fd_bundle_client_reset( ctx );
     425           0 :     ctx->metrics.transport_fail_cnt++;
     426           0 :     *charge_busy = 1;
     427           0 :     return;
     428           0 :   }
     429             : 
     430             :   /* Are we ready to issue a new request? */
     431           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( ctx->grpc_client ) ) ) return;
     432           0 :   long io_ts = fd_bundle_now( ctx );
     433           0 :   if( FD_UNLIKELY( fd_bundle_tile_should_stall( ctx, io_ts ) ) ) return;
     434             : 
     435           0 :   *charge_busy |= fd_bundle_client_step_reconnect( ctx, io_ts );
     436           0 : }
     437             : 
     438             : static void
     439           0 : fd_bundle_client_log_status( fd_bundle_tile_t * ctx ) {
     440           0 :   int status = fd_bundle_client_status( ctx );
     441             : 
     442           0 :   int const connected_now    = ( status==FD_BUNDLE_STATE_CONNECTED );
     443           0 :   int const connected_before = ( ctx->bundle_status_logged==FD_BUNDLE_STATE_CONNECTED );
     444             : 
     445           0 :   if( FD_UNLIKELY( connected_now!=connected_before ) ) {
     446           0 :     long ts = fd_log_wallclock();
     447           0 :     if( FD_LIKELY( ts-(ctx->last_bundle_status_log_nanos) >= (long)1e6 ) ) {
     448           0 :       if( connected_now ) FD_LOG_INFO(( "Connected to bundle server" ));
     449           0 :       else                FD_LOG_INFO(( "Disconnected from bundle server" ));
     450           0 :       ctx->last_bundle_status_log_nanos = ts;
     451           0 :       ctx->bundle_status_logged = (uchar)status;
     452           0 :     }
     453           0 :   }
     454           0 : }
     455             : 
     456             : void
     457             : fd_bundle_client_step( fd_bundle_tile_t * ctx,
     458           0 :                        int *              charge_busy ) {
     459             :   /* Edge-trigger logging with rate limiting */
     460           0 :   fd_bundle_client_step1( ctx, charge_busy );
     461           0 :   fd_bundle_client_log_status( ctx );
     462             : 
     463           0 :   if( FD_UNLIKELY( ctx->tcp_sock_connected && fd_grpc_client_tx_pending( ctx->grpc_client ) ) ) {
     464           0 :     int flush_err = ctx->is_ssl ? fd_grpc_client_tls_flush     ( ctx->grpc_client, ctx->tcp_sock )
     465           0 :                                 : fd_grpc_client_tx_flush_socket( ctx->grpc_client, ctx->tcp_sock );
     466           0 :     if( FD_UNLIKELY( -1==flush_err ) ) {
     467           0 :       fd_bundle_client_reset( ctx );
     468           0 :       ctx->metrics.transport_fail_cnt++;
     469           0 :       *charge_busy = 1;
     470           0 :       return;
     471           0 :     }
     472           0 :   }
     473             : 
     474             :   /* Arm EPOLLOUT only on write demand: unsent HTTP/2 bytes, or a TLS
     475             :      handshake blocked on write (a permanently armed idle-writable
     476             :      socket would keep the epoll set ready forever). */
     477           0 :   if( FD_LIKELY( ctx->tcp_sock_connected && ctx->grpc_client ) ) {
     478           0 :     int want = fd_grpc_client_tx_pending( ctx->grpc_client );
     479           0 :     want |= ctx->is_ssl && fd_grpc_client_tls_tx_pending( ctx->grpc_client );
     480           0 :     fd_bundle_client_epoll_out( ctx, want );
     481           0 :   }
     482           0 : }
     483             : 
     484             : void
     485             : fd_bundle_tile_backoff( fd_bundle_tile_t * ctx,
     486           0 :                         long               now ) {
     487           0 :   uint iter = ctx->backoff_iter;
     488           0 :   if( now < ctx->backoff_reset ) iter = 0U;
     489           0 :   iter++;
     490             : 
     491             :   /* FIXME proper backoff */
     492           0 :   long wait_ns = (long)2e9;
     493           0 :   wait_ns = (long)( fd_rng_ulong( ctx->rng ) & ( (1UL<<fd_ulong_find_msb_w_default( (ulong)wait_ns, 0 ))-1UL ) );
     494             : 
     495           0 :   ctx->backoff_until = now +   wait_ns;
     496           0 :   ctx->backoff_reset = now + 2*wait_ns;
     497             : 
     498           0 :   ctx->backoff_iter = iter;
     499           0 : }
     500             : 
     501             : static void
     502           0 : fd_bundle_client_grpc_conn_established( void * app_ctx ) {
     503           0 :   (void)app_ctx;
     504           0 :   FD_LOG_INFO(( "Bundle gRPC connection established" ));
     505           0 : }
     506             : 
     507             : static void
     508             : fd_bundle_client_grpc_conn_dead( void * app_ctx,
     509             :                                  uint   h2_err,
     510           0 :                                  int    closed_by ) {
     511           0 :   fd_bundle_tile_t * ctx = app_ctx;
     512           0 :   FD_LOG_INFO(( "Bundle gRPC connection closed %s (%u-%s)",
     513           0 :                 closed_by ? "by peer" : "due to error",
     514           0 :                 h2_err, fd_h2_strerror( h2_err ) ));
     515           0 :   ctx->defer_reset = 1;
     516           0 : }
     517             : 
     518             : /* Publishes a transaction to the verify tiles.  Assumes the caller
     519             :    checked that a credit is available.  bundle_txn_cnt is zero for a
     520             :    standalone packet. */
     521             : 
     522             : static void
     523             : fd_bundle_tile_publish_txn( fd_bundle_tile_t * ctx,
     524             :                             void const *       txn,
     525             :                             ulong              txn_sz,  /* <=FD_TXN_MTU */
     526             :                             uint               source_ipv4,
     527           0 :                             ulong              bundle_txn_cnt ) {
     528           0 :   int is_bundle = bundle_txn_cnt>0UL;
     529           0 :   fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->verify_out.mem, ctx->verify_out.chunk );
     530           0 :   *txnm = (fd_txn_m_t) {
     531           0 :     .reference_block_height = 0UL,
     532           0 :     .payload_sz             = (ushort)txn_sz,
     533           0 :     .txn_t_sz               = 0U,
     534           0 :     .source_ipv4            = source_ipv4,
     535           0 :     .source_tpu             = FD_TXN_M_TPU_SOURCE_BUNDLE,
     536           0 :     .first_seen_nanos       = fd_bundle_now( ctx ),
     537           0 :     .block_engine = {
     538           0 :       .bundle_id      = is_bundle ? ctx->bundle_seq : 0UL,
     539           0 :       .bundle_txn_cnt = is_bundle ? bundle_txn_cnt : 1UL,
     540           0 :       .commission     = is_bundle ? (uchar)ctx->builder_commission : (uchar)0,
     541           0 :     },
     542           0 :   };
     543           0 :   if( is_bundle ) fd_memcpy( txnm->block_engine.commission_pubkey, ctx->builder_pubkey, 32UL );
     544           0 :   else            fd_memset( txnm->block_engine.commission_pubkey, 0, 32UL );
     545           0 :   fd_memcpy( fd_txn_m_payload( txnm ), txn, txn_sz );
     546             : 
     547           0 :   ulong sz    = fd_txn_m_realized_footprint( txnm, 0, 0 );
     548           0 :   ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_bundle_now( ctx ) );
     549           0 :   fd_stem_publish( ctx->stem, ctx->verify_out.idx, (ulong)is_bundle, ctx->verify_out.chunk, sz, 0UL, 0UL, tspub );
     550           0 :   ctx->verify_out.chunk = fd_dcache_compact_next( ctx->verify_out.chunk, sz, ctx->verify_out.chunk0, ctx->verify_out.wmark );
     551           0 :   ctx->metrics.txn_received_cnt++;
     552           0 : }
     553             : 
     554             : static inline ulong
     555           0 : fd_bundle_tile_cr_avail( fd_bundle_tile_t const * ctx ) {
     556           0 :   return ctx->stem->cr_avail[ ctx->verify_out.idx ];
     557           0 : }
     558             : 
     559             : /* fd_bundle_packet_t holds the fields of a packet.Packet message used
     560             :    by the bundle client. */
     561             : 
     562             : struct fd_bundle_packet {
     563             :   uchar const * data;
     564             :   ulong         data_sz;
     565             :   int           has_meta;
     566             :   char          addr[ 256 ]; /* packet.Meta.addr, NUL terminated */
     567             : };
     568             : 
     569             : typedef struct fd_bundle_packet fd_bundle_packet_t;
     570             : 
     571             : /* fd_bundle_client_decode_packet decodes a packet.Packet message.
     572             :    Returns packet on success and NULL on decode failure. */
     573             : 
     574             : static fd_bundle_packet_t *
     575             : fd_bundle_client_decode_packet( fd_pb_inbuf_t        in,
     576           0 :                                 fd_bundle_packet_t * packet ) {
     577           0 :   packet->data     = NULL;
     578           0 :   packet->data_sz  = 0UL;
     579           0 :   packet->has_meta = 0;
     580           0 :   packet->addr[0]  = '\0';
     581           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     582           0 :     fd_pb_tlv_t tlv[1];
     583           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) return NULL;
     584           0 :     switch( tlv->field_id ) {
     585           0 :     case 1: /* data */
     586           0 :       packet->data = fd_pb_tlv_bytes( &in, tlv );
     587           0 :       if( FD_UNLIKELY( !packet->data ) ) return NULL;
     588           0 :       packet->data_sz = tlv->len;
     589           0 :       break;
     590           0 :     case 2: { /* meta */
     591           0 :       fd_pb_inbuf_t meta[1];
     592           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, meta ) ) ) return NULL;
     593           0 :       packet->has_meta = 1;
     594           0 :       while( fd_pb_inbuf_sz( meta ) ) {
     595           0 :         fd_pb_tlv_t meta_tlv[1];
     596           0 :         if( FD_UNLIKELY( !fd_pb_tlv_read( meta, meta_tlv ) ) ) return NULL;
     597           0 :         if( meta_tlv->field_id==2U ) { /* addr */
     598           0 :           if( FD_UNLIKELY( !fd_pb_tlv_cstr( meta, meta_tlv, packet->addr, sizeof(packet->addr) ) ) ) return NULL;
     599           0 :         }
     600           0 :         if( FD_UNLIKELY( !fd_pb_tlv_skip( meta, meta_tlv ) ) ) return NULL;
     601           0 :       }
     602           0 :       break;
     603           0 :     }
     604           0 :     default:
     605           0 :       break;
     606           0 :     }
     607           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) return NULL;
     608           0 :   }
     609           0 :   return packet;
     610           0 : }
     611             : 
     612             : /* fd_bundle_client_decode_header_ts decodes the timestamp of a
     613             :    shared.Header message.  Returns the timestamp in nanoseconds since
     614             :    the Unix epoch on success (0 if absent).  Returns -1 on decode
     615             :    failure. */
     616             : 
     617             : static long
     618           0 : fd_bundle_client_decode_header_ts( fd_pb_inbuf_t in ) {
     619           0 :   ulong seconds = 0UL;
     620           0 :   ulong nanos   = 0UL;
     621           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     622           0 :     fd_pb_tlv_t tlv[1];
     623           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) return -1L;
     624           0 :     if( tlv->field_id==1U ) { /* ts */
     625           0 :       fd_pb_inbuf_t ts[1];
     626           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, ts ) ) ) return -1L;
     627           0 :       while( fd_pb_inbuf_sz( ts ) ) {
     628           0 :         fd_pb_tlv_t ts_tlv[1];
     629           0 :         if( FD_UNLIKELY( !fd_pb_tlv_read( ts, ts_tlv ) ) ) return -1L;
     630           0 :         switch( ts_tlv->field_id ) {
     631           0 :         case 1: /* seconds */
     632           0 :           if( FD_UNLIKELY( ts_tlv->wire_type!=FD_PB_WIRE_TYPE_VARINT ) ) return -1L;
     633           0 :           seconds = ts_tlv->varint;
     634           0 :           break;
     635           0 :         case 2: /* nanos */
     636           0 :           if( FD_UNLIKELY( ts_tlv->wire_type!=FD_PB_WIRE_TYPE_VARINT ) ) return -1L;
     637           0 :           nanos = (ulong)(uint)ts_tlv->varint;
     638           0 :           break;
     639           0 :         default:
     640           0 :           break;
     641           0 :         }
     642           0 :         if( FD_UNLIKELY( !fd_pb_tlv_skip( ts, ts_tlv ) ) ) return -1L;
     643           0 :       }
     644           0 :     }
     645           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) return -1L;
     646           0 :   }
     647           0 :   return (long)( seconds*(ulong)1e9 + nanos );
     648           0 : }
     649             : 
     650             : static void
     651             : fd_bundle_client_sample_rx_delay( fd_bundle_tile_t * ctx,
     652           0 :                                   long               ts ) {
     653           0 :   fd_histf_sample( ctx->metrics.msg_rx_delay, fd_ulong_sat_sub( (ulong)ctx->cached_ts, (ulong)ts ) );
     654           0 : }
     655             : 
     656             : static uint
     657             : fd_bundle_packet_ip4( fd_bundle_packet_t const * packet,
     658           0 :                       uint                       dflt ) {
     659           0 :   uint ip4;
     660           0 :   if( packet->has_meta && fd_cstr_to_ip4_addr( packet->addr, &ip4 ) ) return ip4;
     661           0 :   return dflt;
     662           0 : }
     663             : 
     664             : /* Handles a bundle.Bundle message.  Returns 1 on success and 0 on
     665             :    decode failure. */
     666             : 
     667             : static int
     668             : fd_bundle_client_handle_bundle( fd_bundle_tile_t * ctx,
     669           0 :                                 fd_pb_inbuf_t      in ) {
     670           0 :   fd_bundle_packet_t packets[ FD_BUNDLE_CLIENT_MAX_TXN_PER_BUNDLE ];
     671           0 :   ulong cnt = 0UL;
     672           0 :   long  ts  = 0L;
     673             : 
     674           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     675           0 :     fd_pb_tlv_t tlv[1];
     676           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) return 0;
     677           0 :     switch( tlv->field_id ) {
     678           0 :     case 2: { /* header */
     679           0 :       fd_pb_inbuf_t header[1];
     680           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, header ) ) ) return 0;
     681           0 :       ts = fd_bundle_client_decode_header_ts( *header );
     682           0 :       if( FD_UNLIKELY( ts<0L ) ) return 0;
     683           0 :       break;
     684           0 :     }
     685           0 :     case 3: { /* packets */
     686           0 :       fd_pb_inbuf_t sub[1];
     687           0 :       fd_bundle_packet_t packet[1];
     688           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, sub ) ) ) return 0;
     689           0 :       if( FD_UNLIKELY( !fd_bundle_client_decode_packet( *sub, packet ) ) ) return 0;
     690           0 :       if( FD_UNLIKELY( packet->data_sz==0UL || packet->data_sz>FD_TXN_MTU ) ) {
     691           0 :         FD_LOG_WARNING(( "Bundle server delivered a %lu byte packet, dropping bundle", packet->data_sz ));
     692           0 :         return 1;
     693           0 :       }
     694           0 :       if( FD_UNLIKELY( cnt>=FD_BUNDLE_CLIENT_MAX_TXN_PER_BUNDLE ) ) return 1;
     695           0 :       packets[ cnt++ ] = *packet;
     696           0 :       break;
     697           0 :     }
     698           0 :     default:
     699           0 :       break;
     700           0 :     }
     701           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) return 0;
     702           0 :   }
     703             : 
     704           0 :   if( FD_UNLIKELY( fd_bundle_tile_cr_avail( ctx )<cnt ) ) {
     705           0 :     ctx->metrics.backpressure_drop_cnt += cnt;
     706           0 :     return 1;
     707           0 :   }
     708             : 
     709           0 :   ctx->bundle_seq++;
     710           0 :   ctx->metrics.bundle_received_cnt++;
     711           0 :   for( ulong i=0UL; i<cnt; i++ ) {
     712           0 :     uint ip4 = fd_bundle_packet_ip4( &packets[ i ], ctx->server_ip4_addr );
     713           0 :     fd_bundle_tile_publish_txn( ctx, packets[ i ].data, packets[ i ].data_sz, ip4, cnt );
     714           0 :   }
     715           0 :   fd_bundle_client_sample_rx_delay( ctx, ts );
     716           0 :   return 1;
     717           0 : }
     718             : 
     719             : /* Handle a SubscribeBundlesResponse from a SubscribeBundles gRPC call. */
     720             : 
     721             : static void
     722             : fd_bundle_client_handle_bundle_batch(
     723             :     fd_bundle_tile_t * ctx,
     724             :     fd_pb_inbuf_t      in
     725           0 : ) {
     726           0 :   if( FD_UNLIKELY( !ctx->builder_info_avail ) ) {
     727           0 :     ctx->metrics.missing_builder_info_fail_cnt++; /* unreachable */
     728           0 :     return;
     729           0 :   }
     730             : 
     731           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     732           0 :     fd_pb_tlv_t tlv[1];
     733           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) goto fail;
     734           0 :     if( tlv->field_id==1U ) { /* bundles (BundleUuid) */
     735           0 :       fd_pb_inbuf_t uuid[1];
     736           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, uuid ) ) ) goto fail;
     737           0 :       fd_pb_inbuf_t bundle[1] = {{0}};
     738           0 :       while( fd_pb_inbuf_sz( uuid ) ) {
     739           0 :         fd_pb_tlv_t uuid_tlv[1];
     740           0 :         if( FD_UNLIKELY( !fd_pb_tlv_read( uuid, uuid_tlv ) ) ) goto fail;
     741           0 :         if( uuid_tlv->field_id==1U ) { /* bundle */
     742           0 :           if( FD_UNLIKELY( !fd_pb_tlv_submsg( uuid, uuid_tlv, bundle ) ) ) goto fail;
     743           0 :         }
     744           0 :         if( FD_UNLIKELY( !fd_pb_tlv_skip( uuid, uuid_tlv ) ) ) goto fail;
     745           0 :       }
     746           0 :       if( FD_UNLIKELY( !fd_bundle_client_handle_bundle( ctx, *bundle ) ) ) goto fail;
     747           0 :     }
     748           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) goto fail;
     749           0 :   }
     750           0 :   return;
     751             : 
     752           0 : fail:
     753           0 :   ctx->metrics.decode_fail_cnt++;
     754           0 :   FD_LOG_WARNING(( "Protobuf decode of (block_engine.SubscribeBundlesResponse) failed" ));
     755           0 : }
     756             : 
     757             : /* Called for each 'Packet' (a regular transaction) of a
     758             :    SubscribePacketsResponse. */
     759             : 
     760             : static void
     761             : fd_bundle_client_handle_packet( fd_bundle_tile_t *         ctx,
     762           0 :                                 fd_bundle_packet_t const * packet ) {
     763           0 :   if( FD_UNLIKELY( packet->data_sz==0UL || packet->data_sz>FD_TXN_MTU ) ) {
     764           0 :     FD_LOG_WARNING(( "Bundle server delivered a %lu byte packet, ignoring", packet->data_sz ));
     765           0 :     return;
     766           0 :   }
     767           0 :   if( FD_UNLIKELY( !fd_bundle_tile_cr_avail( ctx ) ) ) {
     768           0 :     ctx->metrics.backpressure_drop_cnt++;
     769           0 :     return;
     770           0 :   }
     771           0 :   fd_bundle_tile_publish_txn( ctx, packet->data, packet->data_sz, fd_bundle_packet_ip4( packet, 0U ), 0UL );
     772           0 :   ctx->metrics.packet_received_cnt++;
     773           0 : }
     774             : 
     775             : /* Handle a SubscribePacketsResponse from a SubscribePackets gRPC call. */
     776             : 
     777             : static void
     778             : fd_bundle_client_handle_packet_batch(
     779             :     fd_bundle_tile_t * ctx,
     780             :     fd_pb_inbuf_t      in
     781           0 : ) {
     782           0 :   long ts = 0L;
     783           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     784           0 :     fd_pb_tlv_t tlv[1];
     785           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) goto fail;
     786           0 :     switch( tlv->field_id ) {
     787           0 :     case 1: { /* header */
     788           0 :       fd_pb_inbuf_t header[1];
     789           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, header ) ) ) goto fail;
     790           0 :       ts = fd_bundle_client_decode_header_ts( *header );
     791           0 :       if( FD_UNLIKELY( ts<0L ) ) goto fail;
     792           0 :       break;
     793           0 :     }
     794           0 :     case 2: { /* batch (PacketBatch) */
     795           0 :       fd_pb_inbuf_t batch[1];
     796           0 :       if( FD_UNLIKELY( !fd_pb_tlv_submsg( &in, tlv, batch ) ) ) goto fail;
     797           0 :       while( fd_pb_inbuf_sz( batch ) ) {
     798           0 :         fd_pb_tlv_t batch_tlv[1];
     799           0 :         if( FD_UNLIKELY( !fd_pb_tlv_read( batch, batch_tlv ) ) ) goto fail;
     800           0 :         if( batch_tlv->field_id==1U ) { /* packets */
     801           0 :           fd_pb_inbuf_t sub[1];
     802           0 :           fd_bundle_packet_t packet[1];
     803           0 :           if( FD_UNLIKELY( !fd_pb_tlv_submsg( batch, batch_tlv, sub ) ) ) goto fail;
     804           0 :           if( FD_UNLIKELY( !fd_bundle_client_decode_packet( *sub, packet ) ) ) goto fail;
     805           0 :           fd_bundle_client_handle_packet( ctx, packet );
     806           0 :         }
     807           0 :         if( FD_UNLIKELY( !fd_pb_tlv_skip( batch, batch_tlv ) ) ) goto fail;
     808           0 :       }
     809           0 :       break;
     810           0 :     }
     811           0 :     default:
     812           0 :       break;
     813           0 :     }
     814           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) goto fail;
     815           0 :   }
     816             : 
     817           0 :   fd_bundle_client_sample_rx_delay( ctx, ts );
     818           0 :   return;
     819             : 
     820           0 : fail:
     821           0 :   ctx->metrics.decode_fail_cnt++;
     822           0 :   FD_LOG_WARNING(( "Protobuf decode of (block_engine.SubscribePacketsResponse) failed" ));
     823           0 : }
     824             : 
     825             : /* Handle a BlockBuilderFeeInfoResponse from a GetBlockBuilderFeeInfo
     826             :    gRPC call. */
     827             : 
     828             : static void
     829             : fd_bundle_client_handle_builder_fee_info(
     830             :     fd_bundle_tile_t * ctx,
     831             :     fd_pb_inbuf_t      in
     832           0 : ) {
     833           0 :   char  pubkey[ FD_BASE58_ENCODED_32_SZ ] = {0};
     834           0 :   ulong commission = 0UL;
     835           0 :   while( fd_pb_inbuf_sz( &in ) ) {
     836           0 :     fd_pb_tlv_t tlv[1];
     837           0 :     if( FD_UNLIKELY( !fd_pb_tlv_read( &in, tlv ) ) ) goto decode_fail;
     838           0 :     switch( tlv->field_id ) {
     839           0 :     case 1: /* pubkey */
     840           0 :       if( FD_UNLIKELY( !fd_pb_tlv_cstr( &in, tlv, pubkey, sizeof(pubkey) ) ) ) goto decode_fail;
     841           0 :       break;
     842           0 :     case 2: /* commission */
     843           0 :       if( FD_UNLIKELY( tlv->wire_type!=FD_PB_WIRE_TYPE_VARINT ) ) goto decode_fail;
     844           0 :       commission = tlv->varint;
     845           0 :       break;
     846           0 :     default:
     847           0 :       break;
     848           0 :     }
     849           0 :     if( FD_UNLIKELY( !fd_pb_tlv_skip( &in, tlv ) ) ) goto decode_fail;
     850           0 :   }
     851           0 :   if( FD_UNLIKELY( commission > 100 ) ) {
     852           0 :     ctx->metrics.decode_fail_cnt++;
     853           0 :     FD_LOG_WARNING(( "BlockBuilderFeeInfoResponse commission out of range (0-100): %lu", commission ));
     854           0 :     return;
     855           0 :   }
     856             : 
     857           0 :   fd_pubkey_t decoded_builder_pubkey;
     858           0 :   if( FD_UNLIKELY( !fd_base58_decode_32( pubkey, decoded_builder_pubkey.uc ) ||
     859           0 :                    fd_pubkey_check_zero( &decoded_builder_pubkey ) ) ) {
     860           0 :     FD_LOG_HEXDUMP_WARNING(( "Invalid pubkey in BlockBuilderFeeInfoResponse", pubkey, strnlen( pubkey, sizeof(pubkey) ) ));
     861           0 :     return;
     862           0 :   }
     863             : 
     864           0 :   ctx->builder_commission = (uchar)commission; /* Apply update atomically */
     865           0 :   fd_memcpy( ctx->builder_pubkey, decoded_builder_pubkey.uc, sizeof(ctx->builder_pubkey) );
     866             : 
     867           0 :   long validity_duration_ns = (long)( 60e9 * 5. ); /* 5 minutes */
     868           0 :   ctx->builder_info_avail = 1;
     869           0 :   ctx->builder_info_valid_until = fd_bundle_now( ctx ) + validity_duration_ns;
     870           0 :   return;
     871             : 
     872           0 : decode_fail:
     873           0 :   ctx->metrics.decode_fail_cnt++;
     874           0 :   FD_LOG_WARNING(( "Protobuf decode of (block_engine.BlockBuilderFeeInfoResponse) failed" ));
     875           0 : }
     876             : 
     877             : static void
     878             : fd_bundle_client_grpc_tx_complete(
     879             :     void * app_ctx,
     880             :     ulong  request_ctx
     881           0 : ) {
     882           0 :   (void)app_ctx; (void)request_ctx;
     883           0 : }
     884             : 
     885             : void
     886             : fd_bundle_client_grpc_rx_start(
     887             :     void * app_ctx,
     888             :     ulong  request_ctx
     889           0 : ) {
     890           0 :   fd_bundle_tile_t * ctx = app_ctx;
     891           0 :   switch( request_ctx ) {
     892           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets:
     893           0 :     ctx->packet_subscription_live = 1;
     894           0 :     ctx->packet_subscription_wait = 0;
     895           0 :     break;
     896           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles:
     897           0 :     ctx->bundle_subscription_live = 1;
     898           0 :     ctx->bundle_subscription_wait = 0;
     899           0 :     break;
     900           0 :   }
     901           0 : }
     902             : 
     903             : void
     904             : fd_bundle_client_grpc_rx_msg(
     905             :     void *       app_ctx,
     906             :     void const * protobuf,
     907             :     ulong        protobuf_sz,
     908             :     ulong        request_ctx
     909           0 : ) {
     910           0 :   fd_bundle_tile_t * ctx = app_ctx;
     911           0 :   ctx->metrics.proto_received_bytes += protobuf_sz;
     912           0 :   fd_pb_inbuf_t in[1]; fd_pb_inbuf_init( in, protobuf, protobuf_sz );
     913           0 :   switch( request_ctx ) {
     914           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthChallenge:
     915           0 :     if( FD_UNLIKELY( !fd_bundle_auther_handle_challenge_resp( &ctx->auther, protobuf, protobuf_sz ) ) ) {
     916           0 :       ctx->metrics.decode_fail_cnt++;
     917           0 :       fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
     918           0 :     }
     919           0 :     break;
     920           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthTokens:
     921           0 :     if( FD_UNLIKELY( !fd_bundle_auther_handle_tokens_resp( &ctx->auther, protobuf, protobuf_sz ) ) ) {
     922           0 :       ctx->metrics.decode_fail_cnt++;
     923           0 :       fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
     924           0 :     }
     925           0 :     break;
     926           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles:
     927           0 :     fd_bundle_client_handle_bundle_batch( ctx, *in );
     928           0 :     break;
     929           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets:
     930           0 :     fd_bundle_client_handle_packet_batch( ctx, *in );
     931           0 :     break;
     932           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_GetBlockBuilderFeeInfo:
     933           0 :     fd_bundle_client_handle_builder_fee_info( ctx, *in );
     934           0 :     break;
     935           0 :   default:
     936           0 :     FD_LOG_ERR(( "Received unexpected gRPC message (request_ctx=%lu)", request_ctx ));
     937           0 :   }
     938           0 : }
     939             : 
     940             : static void
     941             : fd_bundle_client_request_failed( fd_bundle_tile_t * ctx,
     942           0 :                                  ulong              request_ctx ) {
     943           0 :   fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
     944           0 :   switch( request_ctx ) {
     945           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthChallenge:
     946           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthTokens:
     947           0 :     fd_bundle_auther_handle_request_fail( &ctx->auther );
     948           0 :     break;
     949           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_GetBlockBuilderFeeInfo:
     950           0 :     ctx->builder_info_wait = 0;
     951           0 :     break;
     952           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets:
     953           0 :     ctx->packet_subscription_live = 0;
     954           0 :     ctx->packet_subscription_wait = 0;
     955           0 :     break;
     956           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles:
     957           0 :     ctx->bundle_subscription_live = 0;
     958           0 :     ctx->bundle_subscription_wait = 0;
     959           0 :     break;
     960           0 :   }
     961           0 : }
     962             : 
     963             : void
     964             : fd_bundle_client_grpc_rx_end(
     965             :     void *                app_ctx,
     966             :     ulong                 request_ctx,
     967             :     fd_grpc_resp_hdrs_t * resp
     968           0 : ) {
     969           0 :   fd_bundle_tile_t * ctx = app_ctx;
     970           0 :   if( FD_UNLIKELY( resp->h2_status!=200 ) ) {
     971           0 :     FD_LOG_WARNING(( "gRPC request failed (HTTP status %u)", resp->h2_status ));
     972           0 :     fd_bundle_client_request_failed( ctx, request_ctx );
     973           0 :     fd_bundle_auther_reset( &ctx->auther );
     974           0 :     ctx->defer_reset = 1;
     975           0 :     return;
     976           0 :   }
     977             : 
     978           0 :   resp->grpc_msg_len = (uint)fd_url_unescape( resp->grpc_msg, resp->grpc_msg_len );
     979           0 :   if( !resp->grpc_msg_len ) {
     980           0 :     fd_memcpy( resp->grpc_msg, "unknown error", 13 );
     981           0 :     resp->grpc_msg_len = 13;
     982           0 :   }
     983             : 
     984           0 :   switch( request_ctx ) {
     985           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets:
     986           0 :     ctx->packet_subscription_live = 0;
     987           0 :     ctx->packet_subscription_wait = 0;
     988           0 :     fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
     989           0 :     ctx->defer_reset = 1;
     990           0 :     FD_LOG_INFO(( "SubscribePackets stream failed (gRPC status %u-%s). Reconnecting ...",
     991           0 :                   resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ) ));
     992           0 :     return;
     993           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles:
     994           0 :     ctx->bundle_subscription_live = 0;
     995           0 :     ctx->bundle_subscription_wait = 0;
     996           0 :     fd_bundle_tile_backoff( ctx, fd_bundle_now( ctx ) );
     997           0 :     ctx->defer_reset = 1;
     998           0 :     FD_LOG_INFO(( "SubscribeBundles stream failed (gRPC status %u-%s). Reconnecting ...",
     999           0 :                   resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ) ));
    1000           0 :     return;
    1001           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_GetBlockBuilderFeeInfo:
    1002           0 :     ctx->builder_info_wait = 0;
    1003           0 :     break;
    1004           0 :   default:
    1005           0 :     break;
    1006           0 :   }
    1007             : 
    1008           0 :   if( FD_UNLIKELY( resp->grpc_status!=FD_GRPC_STATUS_OK ) ) {
    1009           0 :     FD_LOG_INFO(( "gRPC request failed (gRPC status %u-%s): %.*s",
    1010           0 :                   resp->grpc_status, fd_grpc_status_cstr( resp->grpc_status ),
    1011           0 :                   (int)resp->grpc_msg_len, resp->grpc_msg ));
    1012           0 :     fd_bundle_client_request_failed( ctx, request_ctx );
    1013           0 :     if( resp->grpc_status==FD_GRPC_STATUS_UNAUTHENTICATED ||
    1014           0 :         resp->grpc_status==FD_GRPC_STATUS_PERMISSION_DENIED ) {
    1015           0 :       fd_bundle_auther_reset( &ctx->auther );
    1016           0 :     }
    1017           0 :     return;
    1018           0 :   }
    1019           0 : }
    1020             : 
    1021             : void
    1022             : fd_bundle_client_grpc_rx_timeout(
    1023             :     void * app_ctx,
    1024             :     ulong  request_ctx,  /* FD_BUNDLE_CLIENT_REQ_{...} */
    1025             :     int    deadline_kind /* FD_GRPC_DEADLINE_{HEADER|RX_END} */
    1026           0 : ) {
    1027           0 :   (void)deadline_kind;
    1028           0 :   FD_LOG_WARNING(( "Request timed out: %s", fd_bundle_request_ctx_cstr( request_ctx ) ));
    1029           0 :   fd_bundle_tile_t * ctx = app_ctx;
    1030           0 :   ctx->defer_reset = 1;
    1031           0 : }
    1032             : 
    1033             : static void
    1034           0 : fd_bundle_client_grpc_ping_ack( void * app_ctx ) {
    1035           0 :   fd_bundle_tile_t * ctx = app_ctx;
    1036           0 :   long rtt_sample = fd_keepalive_rx( ctx->keepalive, fd_bundle_now( ctx ) );
    1037           0 :   if( FD_LIKELY( rtt_sample ) ) {
    1038           0 :     fd_rtt_sample( ctx->rtt, (float)rtt_sample, 0 );
    1039           0 :     FD_LOG_DEBUG(( "Keepalive ACK" ));
    1040           0 :   }
    1041           0 :   ctx->metrics.ping_ack_cnt++;
    1042           0 : }
    1043             : 
    1044             : fd_grpc_client_callbacks_t fd_bundle_client_grpc_callbacks = {
    1045             :   .conn_established = fd_bundle_client_grpc_conn_established,
    1046             :   .conn_dead        = fd_bundle_client_grpc_conn_dead,
    1047             :   .tx_complete      = fd_bundle_client_grpc_tx_complete,
    1048             :   .rx_start         = fd_bundle_client_grpc_rx_start,
    1049             :   .rx_msg           = fd_bundle_client_grpc_rx_msg,
    1050             :   .rx_end           = fd_bundle_client_grpc_rx_end,
    1051             :   .rx_timeout       = fd_bundle_client_grpc_rx_timeout,
    1052             :   .ping_ack         = fd_bundle_client_grpc_ping_ack,
    1053             : };
    1054             : 
    1055             : int
    1056           0 : fd_bundle_client_status( fd_bundle_tile_t const * ctx ) {
    1057           0 :   if( FD_UNLIKELY( ( !ctx->tcp_sock_connected ) |
    1058           0 :                    ( !ctx->grpc_client        ) ) ) {
    1059           0 :     return FD_BUNDLE_STATE_DISCONNECTED;
    1060           0 :   }
    1061             : 
    1062           0 :   fd_h2_conn_t * conn = fd_grpc_client_h2_conn( ctx->grpc_client );
    1063           0 :   if( FD_UNLIKELY( !conn ) ) {
    1064           0 :     return FD_BUNDLE_STATE_DISCONNECTED; /* no conn */
    1065           0 :   }
    1066           0 :   if( FD_UNLIKELY( conn->flags &
    1067           0 :       ( FD_H2_CONN_FLAGS_DEAD |
    1068           0 :         FD_H2_CONN_FLAGS_SEND_GOAWAY ) ) ) {
    1069           0 :     return FD_BUNDLE_STATE_DISCONNECTED;
    1070           0 :   }
    1071             : 
    1072           0 :   if( FD_UNLIKELY( conn->flags &
    1073           0 :       ( FD_H2_CONN_FLAGS_CLIENT_INITIAL      |
    1074           0 :         FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0 |
    1075           0 :         FD_H2_CONN_FLAGS_WAIT_SETTINGS_0     |
    1076           0 :         FD_H2_CONN_FLAGS_SERVER_INITIAL ) ) ) {
    1077           0 :     return FD_BUNDLE_STATE_CONNECTING; /* connection is not ready */
    1078           0 :   }
    1079             : 
    1080           0 :   if( FD_UNLIKELY( ctx->auther.state != FD_BUNDLE_AUTH_STATE_DONE_WAIT ) ) {
    1081           0 :     return FD_BUNDLE_STATE_CONNECTING; /* not authenticated */
    1082           0 :   }
    1083             : 
    1084           0 :   if( FD_UNLIKELY( ( !ctx->builder_info_avail       ) |
    1085           0 :                    ( !ctx->packet_subscription_live ) |
    1086           0 :                    ( !ctx->bundle_subscription_live ) ) ) {
    1087           0 :     return FD_BUNDLE_STATE_CONNECTING; /* not fully connected */
    1088           0 :   }
    1089             : 
    1090           0 :   if( FD_UNLIKELY( fd_keepalive_is_timeout( ctx->keepalive, fd_bundle_now( ctx ) ) ) ) {
    1091           0 :     return FD_BUNDLE_STATE_DISCONNECTED; /* possible timeout */
    1092           0 :   }
    1093             : 
    1094           0 :   if( FD_UNLIKELY( !fd_grpc_client_is_connected( ctx->grpc_client ) ) ) {
    1095           0 :     return FD_BUNDLE_STATE_CONNECTING;
    1096           0 :   }
    1097             : 
    1098             :   /* As far as we know, the bundle connection is alive and well. */
    1099           0 :   return FD_BUNDLE_STATE_CONNECTED;
    1100           0 : }
    1101             : 
    1102             : #undef DISCONNECTED
    1103             : #undef CONNECTING
    1104             : #undef CONNECTED
    1105             : 
    1106             : FD_FN_CONST char const *
    1107           0 : fd_bundle_request_ctx_cstr( ulong request_ctx ) {
    1108           0 :   switch( request_ctx ) {
    1109           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthChallenge:
    1110           0 :     return "GenerateAuthChallenge";
    1111           0 :   case FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthTokens:
    1112           0 :     return "GenerateAuthTokens";
    1113           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribePackets:
    1114           0 :     return "SubscribePackets";
    1115           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_SubscribeBundles:
    1116           0 :     return "SubscribeBundles";
    1117           0 :   case FD_BUNDLE_CLIENT_REQ_Bundle_GetBlockBuilderFeeInfo:
    1118           0 :     return "GetBlockBuilderFeeInfo";
    1119           0 :   default:
    1120           0 :     return "unknown";
    1121           0 :   }
    1122           0 : }

Generated by: LCOV version 1.14