public synchronized CompletableFuture<Void> start(final ExecutorService executorService) {
    if (!started.compareAndSet(false, true)) {
      return overallFuture;
    }
    futures =
        Stream.concat(stages.stream(), Stream.of(completerStage))
            .map(task -> runWithErrorHandling(executorService, task))
            .collect(toList());
    completerStage
        .getFuture()
        .whenComplete(
            (result, error) -> {
              if (completing.compareAndSet(false, true)) {
                if (error != null) {
                  overallFuture.completeExceptionally(error);
                } else {
                  overallFuture.complete(null);
                }
              }
            });
    overallFuture.exceptionally(
        error -> {
          if (ExceptionUtils.rootCause(error) instanceof CancellationException) {
            abort();
          }
          return null;
        });
    return overallFuture;
  }
Starts execution of the pipeline. Each stage in the pipeline requires a dedicated thread from
the supplied executor service.

@param executorService the {@link ExecutorService} to execute each stage in.
@return a future that will be completed when the pipeline completes. If the pipeline fails or
    is aborted the returned future will be completed exceptionally.
services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipeline.java