def do_rpc(self, name: str, rpc_function: Callable[..., Awaitable[None]]) -> Callable[..., Awaitable[None]]:
        """
        Wraps a given RPC function by producing an awaitable function suitable to be run in the asyncio
        event loop. The wrapped function catches all unhandled exceptions and reports them to the exception
        future, which consumers can await upon to listen for unhandled exceptions.

        The wrapped function also keeps track of the number of outstanding RPCs to synchronize during
        shutdown.
        :param name: The name of this RPC, to be used for logging
        :param rpc_function: The function implementing the RPC
        :return: An awaitable function implementing the RPC
        """
        async def rpc_wrapper(*args, **kwargs):
            log.debug(f"beginning rpc {name}")
            async with self.zero_cond:
                self.count += 1
                log.debug(f"recorded new RPC, {self.count} RPCs outstanding")

            try:
                result = await rpc_function(*args, **kwargs)
            except Exception as exn:
                log.debug(f"RPC failed with exception:")
                log.debug(traceback.format_exc())
                if not self.exception_future.done():
                    self.exception_future.set_exception(exn)
                result = None

            async with self.zero_cond:
                self.count -= 1
                if self.count == 0:
                    log.debug("All RPC completed, signalling completion")
                    if not self.exception_future.done():
                        self.exception_future.set_result(None)
                    self.zero_cond.notify_all()
                log.debug(f"recorded RPC completion, {self.count} RPCs outstanding")

            return result

        return rpc_wrapper
Wraps a given RPC function by producing an awaitable function suitable to be run in the asyncio
        event loop. The wrapped function catches all unhandled exceptions and reports them to the exception
        future, which consumers can await upon to listen for unhandled exceptions.

        The wrapped function also keeps track of the number of outstanding RPCs to synchronize during
        shutdown.
        :param name: The name of this RPC, to be used for logging
        :param rpc_function: The function implementing the RPC
        :return: An awaitable function implementing the RPC