org.libdohj.params.AbstractNamecoinParams.java Source code

Java tutorial

Introduction

Here is the source code for org.libdohj.params.AbstractNamecoinParams.java

Source

/*
 * Copyright 2016 Jeremy Rand.
 *
 * 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.libdohj.params;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Stopwatch;

import org.bitcoinj.core.AltcoinBlock;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin;
import static org.bitcoinj.core.Coin.COIN;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.utils.MonetaryFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.libdohj.core.AltcoinSerializer;
import org.libdohj.core.AuxPoWNetworkParameters;

// TODO: review this

/**
 * Common parameters for Namecoin networks.
 */
public abstract class AbstractNamecoinParams extends NetworkParameters implements AuxPoWNetworkParameters {
    /** Standard format for the NMC denomination. */
    public static final MonetaryFormat NMC;
    /** Standard format for the mNMC denomination. */
    public static final MonetaryFormat MNMC;
    /** Standard format for the uBTC denomination. */
    public static final MonetaryFormat UNMC;

    public static final int AUXPOW_CHAIN_ID = 0x0001; // 1

    /** Currency code for base 1 Namecoin. */
    public static final String CODE_NMC = "NMC";
    /** Currency code for base 1/1,000 Namecoin. */
    public static final String CODE_MNMC = "mNMC";
    /** Currency code for base 1/1,000,000 Namecoin. */
    public static final String CODE_UNMC = "NMC";

    protected int auxpowStartHeight;

    private static final int BLOCK_VERSION_FLAG_AUXPOW = 0x00000100;

    static {
        NMC = MonetaryFormat.BTC.noCode().code(0, CODE_NMC).code(3, CODE_MNMC).code(6, CODE_UNMC);
        MNMC = NMC.shift(3).minDecimals(2).optionalDecimals(2);
        UNMC = NMC.shift(6).minDecimals(0).optionalDecimals(0);
    }

    /** The string returned by getId() for the main, production network where people trade things. */
    public static final String ID_NMC_MAINNET = "org.namecoin.production";
    /** The string returned by getId() for the testnet. */
    public static final String ID_NMC_TESTNET = "org.namecoin.test";

    protected Logger log = LoggerFactory.getLogger(AbstractNamecoinParams.class);

    public static final int NAMECOIN_PROTOCOL_VERSION_GETHEADERS = 38000;

    public AbstractNamecoinParams() {
        super();
        genesisBlock = createGenesis(this);
        interval = INTERVAL;
        targetTimespan = TARGET_TIMESPAN;
        maxTarget = Utils.decodeCompactBits(0x1e0fffffL); // TODO: figure out the Namecoin value of this

        // BIP 43 recommends using these values regardless of which blockchain is in use.
        bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
        bip32HeaderPriv = 0x0488ADE4; //The 4 byte header that serializes in base58 to "xprv"
    }

    private static AltcoinBlock createGenesis(NetworkParameters params) {
        AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS);
        Transaction t = new Transaction(params);
        try {
            // "... choose what comes next.  Lives of your own, or a return to chains. -- V"
            byte[] bytes = Utils.HEX.decode(
                    "04ff7f001c020a024b2e2e2e2063686f6f7365207768617420636f6d6573206e6578742e20204c69766573206f6620796f7572206f776e2c206f7220612072657475726e20746f20636861696e732e202d2d2056");
            t.addInput(new TransactionInput(params, t, bytes));
            ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
            Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode(
                    "04b620369050cd899ffbbc4e8ee51e8c4534a855bb463439d63d235d4779685d8b6f4870a238cf365ac94fa13ef9a2a22cd99d0d5ee86dcabcafce36c7acf43ce5"));
            scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
            t.addOutput(new TransactionOutput(params, t, COIN.multiply(50), scriptPubKeyBytes.toByteArray()));
        } catch (Exception e) {
            // Cannot happen.
            throw new RuntimeException(e);
        }
        genesisBlock.addTransaction(t);
        return genesisBlock;
    }

    @Override
    public Coin getBlockSubsidy(final int height) {
        return COIN.multiply(50).shiftRight(height / getSubsidyDecreaseBlockCount());
    }

    @Override
    public MonetaryFormat getMonetaryFormat() {
        return NMC;
    }

    @Override
    public Coin getMaxMoney() {
        return MAX_MONEY;
    }

    // TODO: this is Bitcoin, need to figure out if it's the same for Namecoin
    @Override
    public Coin getMinNonDustOutput() {
        return Transaction.MIN_NONDUST_OUTPUT;
    }

    @Override
    public String getUriScheme() {
        return "namecoin";
    }

    @Override
    public boolean hasMaxMoney() {
        return true;
    }

    // This is copied from Bitcoin
    /**
     * Checks if we are at a difficulty transition point.
     * @param storedPrev The previous stored block
     * @return If this is a difficulty transition point
     */
    protected boolean isDifficultyTransitionPoint(StoredBlock storedPrev) {
        return ((storedPrev.getHeight() + 1) % this.getInterval()) == 0;
    }

    @Override
    public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore)
            throws VerificationException, BlockStoreException {
        // This is copied verbatim from Bitcoin except for the Namecoin changes marked accordingly
        Block prev = storedPrev.getHeader();

        // Is this supposed to be a difficulty transition point?
        if (!isDifficultyTransitionPoint(storedPrev)) {

            // No ... so check the difficulty didn't actually change.
            if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
                throw new VerificationException("Unexpected change in difficulty at height "
                        + storedPrev.getHeight() + ": " + Long.toHexString(nextBlock.getDifficultyTarget()) + " vs "
                        + Long.toHexString(prev.getDifficultyTarget()));
            return;
        }

        // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
        // two weeks after the initial block chain download.
        final Stopwatch watch = Stopwatch.createStarted();
        StoredBlock cursor = blockStore.get(prev.getHash());

        // Namecoin addition
        int blocksBack = this.getInterval() - 1;
        if (storedPrev.getHeight() >= this.getAuxpowStartHeight()
                && (storedPrev.getHeight() + 1 > this.getInterval())) {
            blocksBack = this.getInterval();
        }

        // Namecoin modification
        //for (int i = 0; i < this.getInterval() - 1; i++) {
        for (int i = 0; i < blocksBack; i++) {
            if (cursor == null) {
                // This should never happen. If it does, it means we are following an incorrect or busted chain.
                throw new VerificationException(
                        "Difficulty transition point but we did not find a way back to the genesis block.");
            }
            cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
        }
        watch.stop();
        if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
            log.info("Difficulty transition traversal took {}", watch);

        Block blockIntervalAgo = cursor.getHeader();
        int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
        // Limit the adjustment step.
        final int targetTimespan = this.getTargetTimespan();
        if (timespan < targetTimespan / 4)
            timespan = targetTimespan / 4;
        if (timespan > targetTimespan * 4)
            timespan = targetTimespan * 4;

        BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
        newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
        newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));

        if (newTarget.compareTo(this.getMaxTarget()) > 0) {
            log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
            newTarget = this.getMaxTarget();
        }

        int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
        long receivedTargetCompact = nextBlock.getDifficultyTarget();

        // The calculated difficulty is to a higher precision than received, so reduce here.
        BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
        newTarget = newTarget.and(mask);
        long newTargetCompact = Utils.encodeCompactBits(newTarget);

        if (newTargetCompact != receivedTargetCompact)
            throw new VerificationException("Network provided difficulty bits do not match what was calculated: "
                    + Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
    }

    @Override
    public int getChainID() {
        return AUXPOW_CHAIN_ID;
    }

    // TODO: re-add this when we introduce Testnet2
    /**
     * Whether this network has special rules to enable minimum difficulty blocks
     * after a long interval between two blocks (i.e. testnet).
     */
    //public abstract boolean allowMinDifficultyBlocks();

    /**
     * Get the hash to use for a block.
     */

    @Override
    public Sha256Hash getBlockDifficultyHash(Block block) {
        return block.getHash();
    }

    @Override
    public AltcoinSerializer getSerializer(boolean parseRetain) {
        return new AltcoinSerializer(this, parseRetain);
    }

    // TODO: look into allowing PeerGroups that don't support GetHeaders (since for full block retrieval it's not needed)
    @Override
    public int getProtocolVersionNum(final ProtocolVersion version) {
        switch (version) {
        case MINIMUM:
            return NAMECOIN_PROTOCOL_VERSION_GETHEADERS;
        default:
            return version.getBitcoinProtocolVersion();
        }
    }

    @Override
    public boolean isAuxPoWBlockVersion(long version) {
        return (version & BLOCK_VERSION_FLAG_AUXPOW) > 0;
    }

    public int getAuxpowStartHeight() {
        return auxpowStartHeight;
    }

    private static class CheckpointEncounteredException extends Exception {

        private CheckpointEncounteredException() {
        }
    }
}