Home Reference Source Test

src/main/generic/miner/MinerWorkerPool.js

/**
 *
 */
class MinerWorkerPool extends IWorker.Pool(MinerWorker) {
    constructor(size = 1) {
        super((name) => IWorker.startWorkerForProxy(MinerWorker, name), 'miner', size);
        /** @type {boolean} */
        this._miningEnabled = false;
        /** @type {Array.<{minNonce: number, maxNonce: number}>} */
        this._activeNonces = [];
        /** @type {Block} */
        this._block = null;
        /** @type {number} */
        this._noncesPerRun = 256;
        /** @type {Observable} */
        this._observable = new Observable();
        /** @type {number} */
        this._shareCompact = Policy.BLOCK_TARGET_MAX;
        /** @type {number} */
        this._runsPerCycle = Infinity;
        /** @type {number} */
        this._cycleWait = 100;

        // FIXME: This is needed for Babel to work correctly. Can be removed as soon as we updated to Babel v7.
        this._superUpdateToSize = super._updateToSize;

        if (PlatformUtils.isNodeJs()) {
            /**
             * @param {SerialBuffer} blockHeader
             * @param {number} compact
             * @param {number} minNonce
             * @param {number} maxNonce
             * @returns {Promise.<{hash: Uint8Array, nonce: number}|boolean>}
             */
            this.multiMine = function (blockHeader, compact, minNonce, maxNonce) {
                return new Promise((resolve, fail) => {
                    NodeNative.node_argon2_target_async(async (nonce) => {
                        try {
                            if (nonce === maxNonce) {
                                resolve(false);
                            } else {
                                blockHeader.writePos -= 4;
                                blockHeader.writeUint32(nonce);
                                const hash = await (await CryptoWorker.getInstanceAsync()).computeArgon2d(blockHeader);
                                resolve({hash, nonce});
                            }
                        } catch (e) {
                            fail(e);
                        }
                    }, blockHeader, compact, minNonce, maxNonce, 512);
                });
            };
        }
    }

    /**
     * @type {number}
     */
    get noncesPerRun() {
        return this._noncesPerRun;
    }

    /**
     * @param {number} nonces
     */
    set noncesPerRun(nonces) {
        this._noncesPerRun = nonces;
    }

    /**
     * @type {number}
     */
    get runsPerCycle() {
        return this._runsPerCycle;
    }

    /**
     * @param {number} runsPerCycle
     */
    set runsPerCycle(runsPerCycle) {
        this._runsPerCycle = runsPerCycle;
    }

    /**
     * @type {number}
     */
    get cycleWait() {
        return this._cycleWait;
    }

    /**
     * @param {number} cycleWait
     */
    set cycleWait(cycleWait) {
        this._cycleWait = cycleWait;
    }

    /**
     * @param {string} type
     * @param {Function} callback
     * @return {number}
     */
    on(type, callback) { this._observable.on(type, callback); }

    /**
     * @param {string} type
     * @param {number} id
     */
    off(type, id) { this._observable.off(type, id); }

    /**
     * @param {Block} block
     * @param {number} [shareCompact] target of a share, in compact format.
     */
    async startMiningOnBlock(block, shareCompact = block.nBits) {
        this._block = block;
        this._shareCompact = shareCompact;
        if (!this._miningEnabled) {
            await this._updateToSize();
            this._activeNonces = [];
            this._miningEnabled = true;
            for (let i = 0; i < this.poolSize; ++i) {
                this._startMiner();
            }
        } else {
            this._activeNonces = [{minNonce:0, maxNonce:0}];
        }
    }

    stop() {
        this._miningEnabled = false;
    }

    async _updateToSize() {
        if (!PlatformUtils.isNodeJs()) {
            await this._superUpdateToSize.call(this);
        }

        while (this._miningEnabled && this._activeNonces.length < this.poolSize) {
            this._startMiner();
        }
    }

    _startMiner() {
        if (this._activeNonces.length >= this.poolSize) {
            return;
        }

        const minNonce = this._activeNonces.length === 0 ? 0 : Math.max.apply(null, this._activeNonces.map((a) => a.maxNonce));
        const maxNonce = minNonce + this._noncesPerRun;
        const nonceRange = {minNonce, maxNonce};
        this._activeNonces.push(nonceRange);
        this._singleMiner(nonceRange).catch((e) => Log.e(MinerWorkerPool, e));
    }

    /**
     * @param {{minNonce: number, maxNonce: number}} nonceRange
     * @return {Promise.<void>}
     * @private
     */
    async _singleMiner(nonceRange) {
        let i = 0;
        while (this._miningEnabled && (IWorker.areWorkersAsync || PlatformUtils.isNodeJs() || i === 0) && i < this._runsPerCycle) {
            i++;
            const block = this._block;
            const result = await this.multiMine(block.header.serialize(), this._shareCompact, nonceRange.minNonce, nonceRange.maxNonce);
            if (result) {
                const hash = new Hash(result.hash);
                this._observable.fire('share', {
                    block,
                    nonce: result.nonce,
                    hash
                });
            } else {
                this._observable.fire('no-share', {
                    nonce: nonceRange.maxNonce
                });
            }
            if (this._activeNonces.length > this.poolSize) {
                this._activeNonces.splice(this._activeNonces.indexOf(nonceRange), 1);
                return;
            } else {
                const newMin = Math.max.apply(null, this._activeNonces.map((a) => a.maxNonce));
                const newRange = {minNonce: newMin, maxNonce: newMin + this._noncesPerRun};
                this._activeNonces.splice(this._activeNonces.indexOf(nonceRange), 1, newRange);
                nonceRange = newRange;
            }
        }
        if (this._miningEnabled) {
            setTimeout(() => this._singleMiner(nonceRange), this._cycleWait);
        }
    }
}

Class.register(MinerWorkerPool);