strat.mining.stratum.proxy.worker.GetworkJobTemplate.java Source code

Java tutorial

Introduction

Here is the source code for strat.mining.stratum.proxy.worker.GetworkJobTemplate.java

Source

/**
 * stratum-proxy is a proxy supporting the crypto-currency stratum pool mining
 * protocol.
 * Copyright (C) 2014  Stratehm (stratehm@hotmail.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with multipool-stats-backend. If not, see <http://www.gnu.org/licenses/>.
 */
package strat.mining.stratum.proxy.worker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import strat.mining.stratum.proxy.CryptoAlgorithm;
import strat.mining.stratum.proxy.exception.UnsupportedCryptoAlgorithmException;
import strat.mining.stratum.proxy.utils.AtomicBigInteger;
import strat.mining.stratum.proxy.utils.mining.SHA256HashingUtils;
import strat.mining.stratum.proxy.utils.mining.ScryptHashingUtils;

import com.google.common.io.BaseEncoding;

/**
 * The template of a Getwork job built from stratum notify values.
 * 
 * @author Strat
 * 
 */
public class GetworkJobTemplate {

    private static final Logger LOGGER = LoggerFactory.getLogger(GetworkJobTemplate.class);

    private static final String HASH1 = "00000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000010000";

    private static final String DEFAULT_TARGET = "00000000ffff0000000000000000000000000000000000000000000000000000";

    // A fake merkle root hash used to fill the templateData
    private static final byte[] FAKE_MERKLE_ROOT = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    // Padding used to fill the block header until 128 bytes (always the same
    // bytes)
    private static final byte[] BLOCK_HEADER_PADDING = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x80, (byte) 0x02, (byte) 0x00, (byte) 0x00 };

    // The default value for the nonce.
    private static final byte[] DEFAULT_NONCE = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    // The first index of the merkle root hash in the block header
    private static final int MERKLE_ROOT_BLOCK_HEADER_POSITION = 36;

    private volatile String jobId;

    private volatile byte[] version;
    private volatile byte[] hashPrevBlock;
    private AtomicBigInteger time;
    private volatile byte[] bits;

    private byte[] nonce;

    // Stratum parameters
    private volatile List<String> merkleBranches;
    private volatile String coinbase1;
    private volatile String coinbase2;
    private volatile String extranonce1;

    private volatile byte[] templateData;

    // Flag to true when templateData has to be updated.
    private volatile boolean isDataDirty = true;

    private volatile double difficulty;
    private volatile String target;
    private volatile BigInteger targetInteger;

    private volatile long lastDataTemplateUpdateTime;

    private final boolean noMidState;

    public GetworkJobTemplate(String jobId, String version, String hashPrevBlock, String time, String bits,
            List<String> merkleBranches, String coinbase1, String coinbase2, String extranonce1,
            boolean noMidState) {
        this.jobId = jobId;
        this.noMidState = noMidState;
        this.merkleBranches = merkleBranches;
        this.coinbase1 = coinbase1;
        this.coinbase2 = coinbase2;
        this.extranonce1 = extranonce1;

        this.hashPrevBlock = BaseEncoding.base16().decode(hashPrevBlock);
        this.version = BaseEncoding.base16().decode(version);
        // Create the time BigInteger from the LittleEndian hex data.
        this.time = new AtomicBigInteger(BaseEncoding.base16().decode(time));
        this.bits = BaseEncoding.base16().decode(bits);
        this.nonce = DEFAULT_NONCE;

        this.lastDataTemplateUpdateTime = System.currentTimeMillis() / 1000;

        this.target = DEFAULT_TARGET;
        this.targetInteger = BigInteger.ONE;

        computeTemplateData();
    }

    public GetworkJobTemplate(GetworkJobTemplate toClone) {
        this.jobId = toClone.jobId;
        this.merkleBranches = new ArrayList<String>(toClone.merkleBranches);
        this.coinbase1 = toClone.coinbase1;
        this.coinbase2 = toClone.coinbase2;
        this.extranonce1 = toClone.extranonce1;

        this.hashPrevBlock = toClone.hashPrevBlock;
        this.version = toClone.version;
        // Create the time BigInteger from the LittleEndian hex data.
        this.time = new AtomicBigInteger(toClone.time);
        this.bits = toClone.bits;
        this.nonce = toClone.nonce;

        this.lastDataTemplateUpdateTime = toClone.lastDataTemplateUpdateTime;

        this.target = DEFAULT_TARGET;
        this.targetInteger = BigInteger.ONE;
        this.noMidState = toClone.noMidState;

        computeTemplateData();
    }

    public String getJobId() {
        return jobId;
    }

    public void setJobId(String jobId) {
        this.jobId = jobId;
    }

    public void setVersion(String version) {
        this.version = BaseEncoding.base16().decode(version);
        isDataDirty = true;
    }

    public void setHashPrevBlock(String hashPrevBlock) {
        this.hashPrevBlock = BaseEncoding.base16().decode(hashPrevBlock);
        isDataDirty = true;
    }

    public void setTime(String time) {
        this.time.set(BaseEncoding.base16().decode(time));
        isDataDirty = true;
    }

    public void setBits(String bits) {
        this.bits = BaseEncoding.base16().decode(bits);
        isDataDirty = true;
    }

    public void setMerkleBranches(List<String> merkleBranches) {
        if (merkleBranches != null && merkleBranches.size() > 0) {
            this.merkleBranches = merkleBranches;
        }
    }

    public void setCoinbase1(String coinbase1) {
        this.coinbase1 = coinbase1;
    }

    public void setCoinbase2(String coinbase2) {
        this.coinbase2 = coinbase2;
    }

    public void setExtranonce1(String extranonce1) {
        this.extranonce1 = extranonce1;
    }

    public void setNonce(String nonce) {
        this.nonce = BaseEncoding.base16().decode(nonce);
        isDataDirty = true;
    }

    /**
     * Compute the template data array.
     */
    private void computeTemplateData() {
        // Compute once again the data only if at least one value has changed or
        // if the last update was more than 1 second ago.
        long currentTime = System.currentTimeMillis() / 1000;
        long secondsSinceLastUpdate = currentTime - lastDataTemplateUpdateTime;
        if (isDataDirty || secondsSinceLastUpdate > 1) {
            isDataDirty = false;
            lastDataTemplateUpdateTime = currentTime;

            // Update the time with the secondsSinceLastUpdate
            time.addAndGet(BigInteger.valueOf(secondsSinceLastUpdate));

            // The block header is 128 Bytes long
            // 80 bytes of useful data and others as padding.
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);

            try {
                // Build the template block header
                byteStream.write(version);
                byteStream.write(hashPrevBlock);
                byteStream.write(FAKE_MERKLE_ROOT);
                byteStream.write(time.get().toByteArray());
                byteStream.write(bits);
                byteStream.write(nonce);
                byteStream.write(BLOCK_HEADER_PADDING);

                templateData = byteStream.toByteArray();
            } catch (IOException e) {
                LOGGER.error(
                        "Failed to update GetworkJobTemplate. version: {}, hashPrevBlock: {}, merkleRoot: {}, time: {}, bits: {}, nonce: {}, padding: {}.",
                        version, hashPrevBlock, FAKE_MERKLE_ROOT, time, bits, nonce, BLOCK_HEADER_PADDING, e);
            }
        }

    }

    /**
     * Return the merkleRoot and the data of this job based on the extranonce2
     * value.
     * 
     * @param extranonce2
     * @return
     */
    public GetworkRequestResult getData(String extranonce2) {
        computeTemplateData();

        // Build the merkleRoot with the given extranonce2
        byte[] bigEndianMerkleRootHash = buildMerkleRootHash(extranonce2);

        // Byte swap the merkleRoot of 4-bytes words.
        byte[] littleEndianMerkleRootHash = strat.mining.stratum.proxy.utils.ArrayUtils
                .swapBytes(bigEndianMerkleRootHash, 4);

        // And reverse the order of the 4-bytes words.
        // littleEndianMerkleRootHash =
        // strat.mining.stratum.proxy.utils.ArrayUtils.reverseWords(littleEndianMerkleRootHash,
        // 4);

        // Then build the data
        byte[] data = buildData(littleEndianMerkleRootHash);

        // Compute midstate only if enabled.
        String midstate = null;
        if (!noMidState) {
            midstate = BaseEncoding.base16().encode(buildMidstate(data));
        }

        GetworkRequestResult result = new GetworkRequestResult();
        result.setMerkleRoot(BaseEncoding.base16().encode(littleEndianMerkleRootHash));
        result.setData(BaseEncoding.base16().encode(data));
        result.setHash1(getHash1());
        result.setTarget(getTarget());
        result.setMidstate(midstate);

        return result;
    }

    /**
     * Build the midstate of the given block header.
     * 
     * @param data
     * @return
     */
    private byte[] buildMidstate(byte[] data) {

        byte[] midstateData = ArrayUtils.subarray(data, 0, 64);

        return SHA256HashingUtils.midstateSHA256(midstateData);
    }

    /**
     * Build the getwork data based on the given littleEndianMerkleRootHash.
     * 
     * @param littleEndianMerkleRootHash
     * @return
     */
    private byte[] buildData(byte[] littleEndianMerkleRootHash) {
        // Clone the templateData
        byte[] data = ArrayUtils.clone(templateData);

        // Then copy the merkleRoot into the data.
        strat.mining.stratum.proxy.utils.ArrayUtils.copyInto(littleEndianMerkleRootHash, data,
                MERKLE_ROOT_BLOCK_HEADER_POSITION);
        return data;
    }

    /**
     * Return the merkleRoot based on the extranonce2. The merkleRoot hash is in
     * BigEndian. So all 32 bits word should be converted in LittleEndian.
     * 
     * @param extranonce2
     * @return
     */
    private byte[] buildMerkleRootHash(String extranonce2) {
        byte[] merkleRoot = buildCoinbaseHash(extranonce2);

        for (String merkleBranch : merkleBranches) {
            merkleRoot = SHA256HashingUtils
                    .doubleSha256Hash(ArrayUtils.addAll(merkleRoot, BaseEncoding.base16().decode(merkleBranch)));
        }

        return merkleRoot;
    }

    /**
     * 
     * @param extranonce2
     * @return
     */
    private byte[] buildCoinbaseHash(String extranonce2) {
        String coinbaseString = coinbase1 + extranonce1 + extranonce2 + coinbase2;
        byte[] rawCoinbase = BaseEncoding.base16().decode(coinbaseString);
        return SHA256HashingUtils.doubleSha256Hash(rawCoinbase);
    }

    public double getDifficulty() {
        return difficulty;
    }

    /**
     * Set the difficulty of the job and compute the target. If scrypt, divide
     * the target by 2^16.
     * 
     * @param difficulty
     * @param isScrypt
     */
    public void setDifficulty(double difficulty, CryptoAlgorithm algo) {
        this.difficulty = difficulty;
        computeTarget(difficulty, algo);
    }

    public String getTarget() {
        return target;
    }

    public BigInteger getTargetInteger() {
        return targetInteger;
    }

    public String getHash1() {
        return HASH1;
    }

    /**
     * Compute the target based on the difficulty.
     * 
     * @param difficulty
     * @param isScrypt
     */
    private void computeTarget(double difficulty, CryptoAlgorithm algo) {
        BigDecimal difficulty1 = new BigDecimal(0);
        switch (algo) {
        case Scrypt:
            difficulty1 = ScryptHashingUtils.DIFFICULTY_1_TARGET;
            break;
        case SHA256:
            difficulty1 = SHA256HashingUtils.DIFFICULTY_1_TARGET;
            break;
        default:
            throw new UnsupportedCryptoAlgorithmException(algo);
        }

        targetInteger = difficulty1.divide(BigDecimal.valueOf(difficulty), 0, RoundingMode.HALF_EVEN)
                .toBigInteger();
        byte[] bigEndianTargetBytes = targetInteger.toByteArray();

        // Build the target on 32 Bytes
        byte[] littleEndianTargetBytes = new byte[32];
        strat.mining.stratum.proxy.utils.ArrayUtils.copyInto(bigEndianTargetBytes, littleEndianTargetBytes,
                32 - bigEndianTargetBytes.length);
        // Then swap bytes from big-endian to little-endian
        littleEndianTargetBytes = strat.mining.stratum.proxy.utils.ArrayUtils.swapBytes(littleEndianTargetBytes, 4);
        // And reverse the order of 4-bytes words (big-endian to little-endian
        // 256-bits integer)
        littleEndianTargetBytes = strat.mining.stratum.proxy.utils.ArrayUtils.reverseWords(littleEndianTargetBytes,
                4);
        this.target = BaseEncoding.base16().encode(littleEndianTargetBytes);
    }

    /**
     * Contains all fields needed to reply to a getwork request.
     * 
     * @author Strat
     * 
     */
    public static class GetworkRequestResult {
        private String data;
        private String target;
        private String hash1;
        private String midstate;
        private String merkleRoot;

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }

        public String getTarget() {
            return target;
        }

        public void setTarget(String target) {
            this.target = target;
        }

        public String getHash1() {
            return hash1;
        }

        public void setHash1(String hash1) {
            this.hash1 = hash1;
        }

        public String getMidstate() {
            return midstate;
        }

        public void setMidstate(String midstate) {
            this.midstate = midstate;
        }

        public String getMerkleRoot() {
            return merkleRoot;
        }

        public void setMerkleRoot(String merkleRoot) {
            this.merkleRoot = merkleRoot;
        }

    }
}