diff --git a/src/main/java/org/bitcoinj/core/AltcoinBlock.java b/src/main/java/org/bitcoinj/core/AltcoinBlock.java index 42f70d0a..5579eb30 100644 --- a/src/main/java/org/bitcoinj/core/AltcoinBlock.java +++ b/src/main/java/org/bitcoinj/core/AltcoinBlock.java @@ -248,12 +248,15 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { protected boolean checkProofOfWork(boolean throwException) throws VerificationException { if (params instanceof AltcoinNetworkParameters) { BigInteger target = getDifficultyTargetAsInteger(); - - final AuxPoWNetworkParameters altParams = (AuxPoWNetworkParameters)this.params; - if (altParams.isAuxPoWBlockVersion(getRawVersion()) && null != auxpow) { - return auxpow.checkProofOfWork(this.getHash(), target, throwException); + + if (params instanceof AuxPoWNetworkParameters) { + final AuxPoWNetworkParameters auxParams = (AuxPoWNetworkParameters)this.params; + if (auxParams.isAuxPoWBlockVersion(getRawVersion()) && null != auxpow) { + return auxpow.checkProofOfWork(this.getHash(), target, throwException); + } } + final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)this.params; BigInteger h = altParams.getBlockDifficultyHash(this).toBigInteger(); if (h.compareTo(target) > 0) { // Proof of work check failed! diff --git a/src/main/java/org/libdohj/params/AbstractLitecoinParams.java b/src/main/java/org/libdohj/params/AbstractLitecoinParams.java new file mode 100644 index 00000000..99a63cfd --- /dev/null +++ b/src/main/java/org/libdohj/params/AbstractLitecoinParams.java @@ -0,0 +1,252 @@ +/* + * Copyright 2013 Google Inc. + * + * 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.math.BigInteger; +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.Sha256Hash; +import org.bitcoinj.core.VerificationException; +import org.bitcoinj.store.BlockStore; +import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.core.StoredBlock; +import org.bitcoinj.core.Utils; +import org.bitcoinj.utils.MonetaryFormat; +import org.libdohj.core.AltcoinNetworkParameters; +import org.libdohj.core.AltcoinSerializer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Common parameters for Litecoin networks. + */ +public abstract class AbstractLitecoinParams extends NetworkParameters implements AltcoinNetworkParameters { + /** Standard format for the LITE denomination. */ + public static final MonetaryFormat LITE; + /** Standard format for the mLITE denomination. */ + public static final MonetaryFormat MLITE; + /** Standard format for the Liteoshi denomination. */ + public static final MonetaryFormat LITEOSHI; + + public static final int LITE_TARGET_TIMESPAN = (int) (3.5 * 24 * 60 * 60); // 3.5 days + public static final int LITE_TARGET_SPACING = (int) (2.5 * 60); // 2.5 minutes + public static final int LITE_INTERVAL = LITE_TARGET_TIMESPAN / LITE_TARGET_SPACING; + + /** + * The maximum number of coins to be generated + */ + public static final long MAX_LITECOINS = 21000000; // TODO: Needs to be 840000000 + + /** + * The maximum money to be generated + */ + public static final Coin MAX_LITECOIN_MONEY = COIN.multiply(MAX_LITECOINS); + + /** Currency code for base 1 Litecoin. */ + public static final String CODE_LITE = "LITE"; + /** Currency code for base 1/1,000 Litecoin. */ + public static final String CODE_MLITE = "mLITE"; + /** Currency code for base 1/100,000,000 Litecoin. */ + public static final String CODE_LITEOSHI = "Liteoshi"; + + static { + LITE = MonetaryFormat.BTC.noCode() + .code(0, CODE_LITE) + .code(3, CODE_MLITE) + .code(7, CODE_LITEOSHI); + MLITE = LITE.shift(3).minDecimals(2).optionalDecimals(2); + LITEOSHI = LITE.shift(7).minDecimals(0).optionalDecimals(2); + } + + /** The string returned by getId() for the main, production network where people trade things. */ + public static final String ID_LITE_MAINNET = "org.litecoin.production"; + /** The string returned by getId() for the testnet. */ + public static final String ID_LITE_TESTNET = "org.litecoin.test"; + + protected Logger log = LoggerFactory.getLogger(AbstractLitecoinParams.class); + public static final int LITECOIN_PROTOCOL_VERSION_MINIMUM = 70002; + public static final int LITECOIN_PROTOCOL_VERSION_CURRENT = 70003; + + public AbstractLitecoinParams() { + super(); + interval = LITE_INTERVAL; + targetTimespan = LITE_TARGET_TIMESPAN; + maxTarget = Utils.decodeCompactBits(0x1e0fffffL); + + packetMagic = 0xfbc0b6db; + bip32HeaderPub = 0x0488C42E; //The 4 byte header that serializes in base58 to "xpub". (?) + bip32HeaderPriv = 0x0488E1F4; //The 4 byte header that serializes in base58 to "xprv" (?) + } + + /** + * Get the hash to use for a block. + */ + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + return ((AltcoinBlock) block).getScryptHash(); + } + + public MonetaryFormat getMonetaryFormat() { + return LITE; + } + + @Override + public Coin getMaxMoney() { + return MAX_LITECOIN_MONEY; + } + + @Override + public Coin getMinNonDustOutput() { + return Coin.COIN; + } + + @Override + public String getUriScheme() { + return "litecoin"; + } + + @Override + public boolean hasMaxMoney() { + return true; + } + + @Override + public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore) + throws VerificationException, BlockStoreException { + final Block prev = storedPrev.getHeader(); + final int previousHeight = storedPrev.getHeight(); + final int retargetInterval = this.getInterval(); + + // Is this supposed to be a difficulty transition point? + if ((storedPrev.getHeight() + 1) % retargetInterval != 0) { + // 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. + StoredBlock cursor = blockStore.get(prev.getHash()); + int goBack = retargetInterval - 1; + if (cursor.getHeight()+1 != retargetInterval) + goBack = retargetInterval; + + for (int i = 0; i < goBack; 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()); + } + + //We used checkpoints... + if (cursor == null) { + log.debug("Difficulty transition: Hit checkpoint!"); + return; + } + + Block blockIntervalAgo = cursor.getHeader(); + long receivedTargetCompact = nextBlock.getDifficultyTarget(); + long newTargetCompact = this.getNewDifficultyTarget(previousHeight, prev, + nextBlock, blockIntervalAgo); + + if (newTargetCompact != receivedTargetCompact) + throw new VerificationException("Network provided difficulty bits do not match what was calculated: " + + newTargetCompact + " vs " + receivedTargetCompact); + } + + /** + * + * @param previousHeight height of the block immediately before the retarget. + * @param prev the block immediately before the retarget block. + * @param nextBlock the block the retarget happens at. + * @param blockIntervalAgo The last retarget block. + * @return New difficulty target as compact bytes. + */ + public long getNewDifficultyTarget(int previousHeight, final Block prev, final Block nextBlock, + final Block blockIntervalAgo) { + return this.getNewDifficultyTarget(previousHeight, prev.getTimeSeconds(), + prev.getDifficultyTarget(), blockIntervalAgo.getTimeSeconds(), + nextBlock.getDifficultyTarget()); + } + + /** + * + * @param previousHeight Height of the block immediately previous to the one we're calculating difficulty of. + * @param previousBlockTime Time of the block immediately previous to the one we're calculating difficulty of. + * @param lastDifficultyTarget Compact difficulty target of the last retarget block. + * @param lastRetargetTime Time of the last difficulty retarget. + * @param nextDifficultyTarget The expected difficulty target of the next + * block, used for determining precision of the result. + * @return New difficulty target as compact bytes. + */ + protected long getNewDifficultyTarget(int previousHeight, long previousBlockTime, + final long lastDifficultyTarget, final long lastRetargetTime, + final long nextDifficultyTarget) { + final int retargetTimespan = this.getTargetTimespan(); + int actualTime = (int) (previousBlockTime - lastRetargetTime); + final int minTimespan = retargetTimespan / 4; + final int maxTimespan = retargetTimespan * 4; + + actualTime = Math.min(maxTimespan, Math.max(minTimespan, actualTime)); + + BigInteger newTarget = Utils.decodeCompactBits(lastDifficultyTarget); + newTarget = newTarget.multiply(BigInteger.valueOf(actualTime)); + newTarget = newTarget.divide(BigInteger.valueOf(retargetTimespan)); + + if (newTarget.compareTo(this.getMaxTarget()) > 0) { + log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16)); + newTarget = this.getMaxTarget(); + } + + int accuracyBytes = (int) (nextDifficultyTarget >>> 24) - 3; + + // 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); + return Utils.encodeCompactBits(newTarget); + } + + @Override + public AltcoinSerializer getSerializer(boolean parseRetain) { + return new AltcoinSerializer(this, parseRetain); + } + + @Override + public int getProtocolVersionNum(final ProtocolVersion version) { + switch (version) { + case PONG: + case BLOOM_FILTER: + return version.getBitcoinProtocolVersion(); + case CURRENT: + return LITECOIN_PROTOCOL_VERSION_CURRENT; + case MINIMUM: + default: + return LITECOIN_PROTOCOL_VERSION_MINIMUM; + } + } +} diff --git a/src/main/java/org/libdohj/params/LitecoinMainNetParams.java b/src/main/java/org/libdohj/params/LitecoinMainNetParams.java new file mode 100644 index 00000000..f06750b8 --- /dev/null +++ b/src/main/java/org/libdohj/params/LitecoinMainNetParams.java @@ -0,0 +1,123 @@ +/* + * Copyright 2013 Google Inc. + * Copyright 2014 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.libdohj.params; + +import org.bitcoinj.core.Utils; +import org.spongycastle.util.encoders.Hex; + +import static com.google.common.base.Preconditions.checkState; +import java.io.ByteArrayOutputStream; +import org.bitcoinj.core.AltcoinBlock; +import org.bitcoinj.core.Block; +import static org.bitcoinj.core.Coin.COIN; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptOpCodes; + +/** + * Parameters for the Litecoin main production network on which people trade + * goods and services. + */ +public class LitecoinMainNetParams extends AbstractLitecoinParams { + public static final int MAINNET_MAJORITY_WINDOW = 1000; + public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 950; + public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 750; + + public LitecoinMainNetParams() { + super(); + id = ID_LITE_MAINNET; + // Genesis hash is 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2 + packetMagic = 0xfbc0b6db; + + maxTarget = Utils.decodeCompactBits(0x1e0fffffL); + port = 9333; + addressHeader = 48; + p2shHeader = 5; + acceptableAddressCodes = new int[] { addressHeader, p2shHeader }; + dumpedPrivateKeyHeader = 176; + + this.genesisBlock = createGenesis(this); + spendableCoinbaseDepth = 30; + subsidyDecreaseBlockCount = 100000; + + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2")); + alertSigningKey = Hex.decode("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9"); + + majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED; + majorityWindow = MAINNET_MAJORITY_WINDOW; + + dnsSeeds = new String[] { + "dnsseed.litecointools.com", + "dnsseed.litecoinpool.org", + "dnsseed.ltc.xurious.com", + "dnsseed.koin-project.com", + "dnsseed.weminemnc.com" + }; + // Note this is the same as the BIP32 testnet, as BIP44 makes HD wallets + // chain agnostic. Litecoin mainnet has its own headers for legacy reasons. + bip32HeaderPub = 0x043587CF; + bip32HeaderPriv = 0x04358394; + } + + private static AltcoinBlock createGenesis(NetworkParameters params) { + AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS); + Transaction t = new Transaction(params); + try { + byte[] bytes = Hex.decode + ("04ffff001d0104404e592054696d65732030352f4f63742f32303131205374657665204a6f62732c204170706c65e280997320566973696f6e6172792c2044696573206174203536"); + t.addInput(new TransactionInput(params, t, bytes)); + ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream(); + Script.writeBytes(scriptPubKeyBytes, Hex.decode + ("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9")); + 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); + genesisBlock.setTime(1317972665L); + genesisBlock.setDifficultyTarget(0x1e0ffff0L); + genesisBlock.setNonce(2084524493); + return genesisBlock; + } + + private static LitecoinMainNetParams instance; + public static synchronized LitecoinMainNetParams get() { + if (instance == null) { + instance = new LitecoinMainNetParams(); + } + return instance; + } + + @Override + public String getPaymentProtocolId() { + // TODO: CHANGE ME + return PAYMENT_PROTOCOL_ID_MAINNET; + } + + @Override + public boolean isTestNet() { + return false; + } +} diff --git a/src/main/java/org/libdohj/params/LitecoinTestNet3Params.java b/src/main/java/org/libdohj/params/LitecoinTestNet3Params.java new file mode 100644 index 00000000..7d67b9f0 --- /dev/null +++ b/src/main/java/org/libdohj/params/LitecoinTestNet3Params.java @@ -0,0 +1,122 @@ +/* + * Copyright 2013 Google Inc. + * Copyright 2014 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.libdohj.params; + +import org.bitcoinj.core.Utils; +import org.spongycastle.util.encoders.Hex; + +import static com.google.common.base.Preconditions.checkState; +import java.io.ByteArrayOutputStream; +import org.bitcoinj.core.AltcoinBlock; +import org.bitcoinj.core.Block; +import static org.bitcoinj.core.Coin.COIN; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptOpCodes; + +/** + * Parameters for the testnet, a separate public instance of Litecoin that has + * relaxed rules suitable for development and testing of applications and new + * Litecoin versions. + */ +public class LitecoinTestNet3Params extends AbstractLitecoinParams { + public static final int TESTNET_MAJORITY_WINDOW = 1000; + public static final int TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED = 750; + public static final int TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 501; + + public LitecoinTestNet3Params() { + super(); + id = ID_LITE_TESTNET; + // Genesis hash is f5ae71e26c74beacc88382716aced69cddf3dffff24f384e1808905e0188f68f + packetMagic = 0xfcc1b7dc; + + maxTarget = Utils.decodeCompactBits(0x1e0fffffL); + port = 19333; + addressHeader = 111; + p2shHeader = 196; + acceptableAddressCodes = new int[] { addressHeader, p2shHeader }; + dumpedPrivateKeyHeader = 239; + + this.genesisBlock = createGenesis(this); + spendableCoinbaseDepth = 30; + subsidyDecreaseBlockCount = 100000; + + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("f5ae71e26c74beacc88382716aced69cddf3dffff24f384e1808905e0188f68f")); + alertSigningKey = Hex.decode("0449623fc74489a947c4b15d579115591add020e53b3490bf47297dfa3762250625f8ecc2fb4fc59f69bdce8f7080f3167808276ed2c79d297054367566038aa82"); + + majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED; + majorityWindow = TESTNET_MAJORITY_WINDOW; + + dnsSeeds = new String[] { + "testnet-seed.litecointools.com", + "testnet-seed.ltc.xurious.com", + "dnsseed.wemine-testnet.com" + }; + // Note this is the same as the BIP32 testnet, as BIP44 makes HD wallets + // chain agnostic. Litecoin mainnet has its own headers for legacy reasons. + bip32HeaderPub = 0x043587CF; + bip32HeaderPriv = 0x04358394; + } + + private static AltcoinBlock createGenesis(NetworkParameters params) { + AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS); + Transaction t = new Transaction(params); + try { + byte[] bytes = Hex.decode + ("04ffff001d0104404e592054696d65732030352f4f63742f32303131205374657665204a6f62732c204170706c65e280997320566973696f6e6172792c2044696573206174203536"); + t.addInput(new TransactionInput(params, t, bytes)); + ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream(); + Script.writeBytes(scriptPubKeyBytes, Hex.decode + ("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9")); + 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); + genesisBlock.setTime(1317798646L); + genesisBlock.setDifficultyTarget(0x1e0ffff0L); + genesisBlock.setNonce(385270584); + return genesisBlock; + } + + private static LitecoinTestNet3Params instance; + public static synchronized LitecoinTestNet3Params get() { + if (instance == null) { + instance = new LitecoinTestNet3Params(); + } + return instance; + } + + @Override + public String getPaymentProtocolId() { + // TODO: CHANGE ME + return PAYMENT_PROTOCOL_ID_TESTNET; + } + + @Override + public boolean isTestNet() { + return true; + } +} diff --git a/src/test/java/org/bitcoinj/core/LitecoinBlockTest.java b/src/test/java/org/bitcoinj/core/LitecoinBlockTest.java new file mode 100644 index 00000000..0459c425 --- /dev/null +++ b/src/test/java/org/bitcoinj/core/LitecoinBlockTest.java @@ -0,0 +1,41 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.bitcoinj.core; + +import java.io.IOException; + +import org.libdohj.core.AltcoinSerializer; +import org.libdohj.params.LitecoinMainNetParams; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author jrn + */ +public class LitecoinBlockTest { + private NetworkParameters params = LitecoinMainNetParams.get(); + + @Before + public void setUp() throws Exception { + Context context = new Context(params); + } + + @Test + public void shouldParseBlock1() throws IOException { + byte[] payload = Util.getBytes(getClass().getResourceAsStream("litecoin_block1.bin")); + AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); + final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); + assertEquals("80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f", block.getHashAsString()); + assertEquals(params.getGenesisBlock().getHash(), block.getPrevBlockHash()); + assertEquals(1, block.getTransactions().size()); + assertEquals(0x1e0ffff0L, block.getDifficultyTarget()); + assertTrue(block.checkProofOfWork(false)); + } +} diff --git a/src/test/resources/org/bitcoinj/core/litecoin_block1.bin b/src/test/resources/org/bitcoinj/core/litecoin_block1.bin new file mode 100644 index 00000000..e0e14ea9 Binary files /dev/null and b/src/test/resources/org/bitcoinj/core/litecoin_block1.bin differ