/* * Copyright 2013 Google Inc. * Copyright 2015 Andreas Schildbach * * 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.bitcoinj.params; import static com.google.common.base.Preconditions.checkState; import java.math.BigInteger; import java.util.concurrent.TimeUnit; import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Utils; import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.core.VerificationException; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Stopwatch; import org.bitcoinj.core.BitcoinSerializer; /** * Parameters for Bitcoin-like networks. */ public abstract class AbstractBitcoinNetParams extends NetworkParameters { /** * Scheme part for Bitcoin URIs. */ public static final String BITCOIN_SCHEME = "bitcoin"; public static final int REWARD_HALVING_INTERVAL = 210000; private static final Logger log = LoggerFactory.getLogger(AbstractBitcoinNetParams.class); public AbstractBitcoinNetParams() { super(); } /** * Checks if we are at a reward halving point. * @param height The height of the previous stored block * @return If this is a reward halving point */ public final boolean isRewardHalvingPoint(final int height) { return ((height + 1) % REWARD_HALVING_INTERVAL) == 0; } /** * Checks if we are at a difficulty transition point. * @param height The height of the previous stored block * @return If this is a difficulty transition point */ public final boolean isDifficultyTransitionPoint(final int height) { return ((height + 1) % this.getInterval()) == 0; } @Override public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock, final BlockStore blockStore) throws VerificationException, BlockStoreException { final Block prev = storedPrev.getHeader(); // Is this supposed to be a difficulty transition point? if (!isDifficultyTransitionPoint(storedPrev.getHeight())) { // 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(); Sha256Hash hash = prev.getHash(); StoredBlock cursor = null; final int interval = this.getInterval(); for (int i = 0; i < interval; i++) { cursor = blockStore.get(hash); 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 last transition point. Not found: " + hash); } hash = cursor.getHeader().getPrevBlockHash(); } checkState(cursor != null && isDifficultyTransitionPoint(cursor.getHeight() - 1), "Didn't arrive at a transition point."); 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 Coin getMaxMoney() { return MAX_MONEY; } @Override public Coin getMinNonDustOutput() { return Transaction.MIN_NONDUST_OUTPUT; } @Override public MonetaryFormat getMonetaryFormat() { return new MonetaryFormat(); } @Override public int getProtocolVersionNum(final ProtocolVersion version) { return version.getBitcoinProtocolVersion(); } @Override public BitcoinSerializer getSerializer(boolean parseRetain) { return new BitcoinSerializer(this, parseRetain); } @Override public String getUriScheme() { return BITCOIN_SCHEME; } @Override public boolean hasMaxMoney() { return true; } }