public CompletableFuture<Short> undeleteAsync(MessageInfo info) {
    try {
      checkStarted();
      // TODO: Add retries similar to synchronous operation
      return undeleteAsyncIfNeeded((BlobId) info.getStoreKey(), info.getLifeVersion()).exceptionally(throwable -> {
        unwrapCompletionExceptionAndThrowStoreException(throwable, CloudStorageException.class,
            this::mapToStoreException);
        return null;
      });
    } catch (StoreException e) {
      return FutureUtils.completedExceptionally(e);
    }
  }
Undelete the blob identified by {@code id} in the store asynchronously. When the lifeVersion is
{@link MessageInfo#LIFE_VERSION_FROM_FRONTEND}, this method is invoked by the responding to the frontend request.
Otherwise, it's invoked in the replication thread.
@param info The {@link MessageInfo} that carries some basic information about this operation. Only the StoreKey,
            OperationTime, LifeVersion should be used in this method.
@return a {@link CompletableFuture} that will eventually contain the lifeVersion of the undeleted blob or the
        {@link StoreException} if an error occurred.
ambry-cloud/src/main/java/com/github/ambry/cloud/CloudBlobStore.java