org.hyperledger.accountimport.KeyPairGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperledger.accountimport.KeyPairGenerator.java

Source

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hyperledger.accountimport;

import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.hyperledger.common.MasterPrivateKey;
import org.hyperledger.common.MasterPublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.LongStream.range;
import static java.util.stream.LongStream.rangeClosed;

/**
 * This class enables the performant generation and storage of private-public key
 * pairs
 */
public class KeyPairGenerator {

    private static final Logger log = LoggerFactory.getLogger(KeyPairGenerator.class);

    /**
     * The number of key pairs being generated per thread - this constant is used
     * only in the sync-free version
     */
    private static final int PER_THREAD_BLOCK_SIZE = 1000;

    /**
     * Drives how frequently should we log the creation of key pairs
     */
    private static final long LOGGING_FREQUENCY_FOR_KEYPAIRS = 2000;

    private final BufferedWriter bufferedWriter;

    private final ForkJoinPool executorService;

    private final SummaryStatistics statistics = new SummaryStatistics();

    private final int generationBlockSize;

    /**
     * Fully initializes the object by allocating the extra ForkJoin thread pool etc
     *
     * @param threadCount  the number of threads to be used as a maximum
     *                     (depending on the algorithm being used it might
     *                     be less but not more)
     * @param outputStream the stream to where the component should write its output
     */
    public KeyPairGenerator(final int threadCount, final OutputStream outputStream) {
        bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        executorService = new ForkJoinPool(threadCount);
        generationBlockSize = threadCount * PER_THREAD_BLOCK_SIZE;
    }

    /**
     * This version of the generation uses 2 levels of java 8 streams:
     * 1) at the top there is a serial stream, summarizing the statistics and
     * persisting into the output stream happen at this level
     * 2) at the lower level there is a parallel stream created by createKeyPairBlockStream
     * doing the generation and serialization in parallel
     * <p>
     *
     * @param countToGenerate the number of key pairs to be generated
     */
    public void generate(final long countToGenerate) {
        log.info("Starting to generate {} key pairs using the two layered stream approach", countToGenerate);
        // at this level the processing is sequential in order to write the results into the output
        // stream without old school synchronisation
        final long blockCount = countToGenerate / generationBlockSize + 1;

        rangeClosed(0, blockCount).mapToObj(blockIndex -> {
            log.info("Starting to work on block {} out of {}", blockIndex, blockCount);
            final long upperBound = Math.min(countToGenerate, (blockIndex + 1) * generationBlockSize);
            final long blockSize = upperBound - blockIndex * generationBlockSize;
            return createKeyPairBlock(blockSize);
        }).forEach(generatedPairs -> {
            generatedPairs.forEach(this::persistKeyPair);
        });
        flushWriter();
        logDurationStatistics();
    }

    /**
     * This version of the generation uses a single parallel stream to generate
     * the key pairs and executes the statistics gathering and the handling of
     * the output stream in an explicite synchronized block.
     *
     * @param countToGenerate the number of key pairs to generate
     */
    public void generateSync(final long countToGenerate) {
        log.info("Starting to generate {} key pairs using the single stream approach", countToGenerate);
        final Runnable fullGeneration = () -> createKeyPairBlockStream(countToGenerate).forEach(generatedPair -> {
            synchronized (bufferedWriter) {
                persistKeyPair(generatedPair);
            }
        }

        );
        // executing this stream definition on the custon thread pool in order
        // to guarantee the number of threads being used
        executorService.submit(fullGeneration).join();
        flushWriter();
        logDurationStatistics();
    }

    /**
     * Returns a read-only view of the statistics captured during generation
     *
     * @return the captured generation statistics
     */
    public StatisticalSummary getGenerationStatistics() {
        return statistics;
    }

    private void flushWriter() {
        try {
            bufferedWriter.flush();
        } catch (final IOException e) {
            throw new RuntimeException("Failed to flush the output writer", e);
        }
    }

    /**
     * This private method is being used by both versions of the generation
     * algorithms implemented by this class It creates a parallel stream being
     * capable of generating key pairs also doing the serialization in parallel.
     *
     * @param size the length of the parallel stream to be created
     * @return a parallel stream ready to be executed
     */
    private Stream<KeyPair> createKeyPairBlockStream(final long size) {
        return range(0, size).parallel().mapToObj(index -> {
            final long startTime = System.nanoTime();
            final MasterPrivateKey privateKey = MasterPrivateKey.createNew();
            final MasterPublicKey publicKey = privateKey.getMasterPublic();
            return new KeyPair(System.nanoTime() - startTime, privateKey.serialize(true),
                    publicKey.serialize(true));
        });
    }

    /**
     * This private method creates a list containing size amount of key pairs.
     * The returned data structure is guaranteed to be safely initialized by the
     * stream framework
     *
     * @param size the number of key pairs to be generated
     * @return a thread safe list containing the generated key pairs
     */
    private List<KeyPair> createKeyPairBlock(final long size) {
        try {
            final Callable<List<KeyPair>> keyGeneration = () -> createKeyPairBlockStream(size)
                    .collect(Collectors.toList());
            return executorService.submit(keyGeneration).get();
        } catch (final ExecutionException e) {
            throw new RuntimeException("The parallel execution has failed in the fork-join framework", e);
        } catch (final InterruptedException e) {
            throw new RuntimeException("An executor thread got interrupted while executing the generation", e);
        }
    }

    private void persistKeyPair(final KeyPair keyPair) {
        try {
            bufferedWriter.write(keyPair.privateKey);
            bufferedWriter.newLine();
            bufferedWriter.write(keyPair.publicKey);
            bufferedWriter.newLine();
            statistics.addValue(keyPair.duration);
            if (statistics.getN() % LOGGING_FREQUENCY_FOR_KEYPAIRS == 0) {
                log.info("Generated and persisted {} key pairs", statistics.getN());
            }
        } catch (final IOException e) {
            throw new RuntimeException("An error has occurred while writing to the output stream", e);
        }
    }

    private void logDurationStatistics() {
        log.info("Captured the following statistics regarding the generation durations:");
        log.info(statistics.toString());
    }

    /**
     * An immutable internal data structure used to exchange data between stages of the
     * processing
     */
    static class KeyPair {

        final long duration;

        final String privateKey;

        final String publicKey;

        KeyPair(final long duration, final String privateKey, final String publicKey) {
            this.duration = duration;
            this.privateKey = privateKey;
            this.publicKey = publicKey;
        }

    }

}