From 0cb659ca21175bedaf4b8d0245864786b6806229 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:52:58 +0000 Subject: [PATCH] Added network parameters for Namecoin (abstract and mainnet). --- .../params/AbstractNamecoinParams.java | 291 ++++++++++++++++++ .../libdohj/params/NamecoinMainNetParams.java | 120 ++++++++ 2 files changed, 411 insertions(+) create mode 100644 core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java create mode 100644 core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java diff --git a/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java b/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java new file mode 100644 index 00000000..c0d6165a --- /dev/null +++ b/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java @@ -0,0 +1,291 @@ +/* + * 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() { + } + } +} diff --git a/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java b/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java new file mode 100644 index 00000000..0da4f6dd --- /dev/null +++ b/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013 Google Inc, 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 org.bitcoinj.core.Sha256Hash; +import org.spongycastle.util.encoders.Hex; + +import static com.google.common.base.Preconditions.checkState; + +// TODO: review this + +/** + * Parameters for the main Namecoin production network on which people trade + * goods and services. + */ +public class NamecoinMainNetParams extends AbstractNamecoinParams { + 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 NamecoinMainNetParams() { + super(); + dumpedPrivateKeyHeader = 180; //This is always addressHeader + 128 + addressHeader = 52; + p2shHeader = 13; + //acceptableAddressCodes = new int[] { addressHeader, p2shHeader }; + acceptableAddressCodes = new int[] { addressHeader }; // Namecoin doesn't yet enforce P2SH, so we disable it for now. + port = 8334; + packetMagic = 0xf9beb4fe; + + genesisBlock.setDifficultyTarget(0x1C007FFFL); + genesisBlock.setTime(1303000001L); + genesisBlock.setNonce(2719916434L); + id = ID_NMC_MAINNET; + subsidyDecreaseBlockCount = 210000; + spendableCoinbaseDepth = 100; + auxpowStartHeight = 19200; + + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"), + genesisHash); + // TODO: remove alert key since it's removed from Bitcoin Core / Namecoin Core + alertSigningKey = Hex.decode("04ba207043c1575208f08ea6ac27ed2aedd4f84e70b874db129acb08e6109a3bbb7c479ae22565973ebf0ac0391514511a22cb9345bdb772be20cfbd38be578b0c"); + + majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED; + majorityWindow = MAINNET_MAJORITY_WINDOW; + + // TODO: check whether there are any non BIP30 blocks in Namecoin; add them here if they exist + // This contains (at a minimum) the blocks which are not BIP30 compliant. BIP30 changed how duplicate + // transactions are handled. Duplicated transactions could occur in the case where a coinbase had the same + // extraNonce and the same outputs but appeared at different heights, and greatly complicated re-org handling. + // Having these here simplifies block connection logic considerably. + checkpoints.put( 2016, Sha256Hash.wrap("0000000000660bad0d9fbde55ba7ee14ddf766ed5f527e3fbca523ac11460b92")); + checkpoints.put( 4032, Sha256Hash.wrap("0000000000493b5696ad482deb79da835fe2385304b841beef1938655ddbc411")); + checkpoints.put( 6048, Sha256Hash.wrap("000000000027939a2e1d8bb63f36c47da858e56d570f143e67e85068943470c9")); + checkpoints.put( 8064, Sha256Hash.wrap("000000000003a01f708da7396e54d081701ea406ed163e519589717d8b7c95a5")); + checkpoints.put( 10080, Sha256Hash.wrap("00000000000fed3899f818b2228b4f01b9a0a7eeee907abd172852df71c64b06")); + checkpoints.put( 12096, Sha256Hash.wrap("0000000000006c06988ff361f124314f9f4bb45b6997d90a7ee4cedf434c670f")); + checkpoints.put( 14112, Sha256Hash.wrap("00000000000045d95e0588c47c17d593c7b5cb4fb1e56213d1b3843c1773df2b")); + checkpoints.put( 16128, Sha256Hash.wrap("000000000001d9964f9483f9096cf9d6c6c2886ed1e5dec95ad2aeec3ce72fa9")); + checkpoints.put( 18940, Sha256Hash.wrap("00000000000087f7fc0c8085217503ba86f796fa4984f7e5a08b6c4c12906c05")); + checkpoints.put( 30240, Sha256Hash.wrap("e1c8c862ff342358384d4c22fa6ea5f669f3e1cdcf34111f8017371c3c0be1da")); + checkpoints.put( 57000, Sha256Hash.wrap("aa3ec60168a0200799e362e2b572ee01f3c3852030d07d036e0aa884ec61f203")); + checkpoints.put(112896, Sha256Hash.wrap("73f880e78a04dd6a31efc8abf7ca5db4e262c4ae130d559730d6ccb8808095bf")); + checkpoints.put(182000, Sha256Hash.wrap("d47b4a8fd282f635d66ce34ebbeb26ffd64c35b41f286646598abfd813cba6d9")); + checkpoints.put(193000, Sha256Hash.wrap("3b85e70ba7f5433049cfbcf0ae35ed869496dbedcd1c0fafadb0284ec81d7b58")); + + dnsSeeds = new String[] { + "namecoindnsseed.digi-masters.com", // George Lloyd + "namecoindnsseed.digi-masters.uk", // George Lloyd + "seed.namecoin.domob.eu", // Daniel Kraft + "nmc.seed.quisquis.de", // Peter Conrad + "dnsseed.namecoin.webbtc.com", // Marius Hanne + }; + + // TODO: look into HTTP seeds or Addr seeds as is done for Bitcoin + } + + private static NamecoinMainNetParams instance; + public static synchronized NamecoinMainNetParams get() { + if (instance == null) { + instance = new NamecoinMainNetParams(); + } + return instance; + } + + // TODO: re-add this when we introduce Testnet2 + /* + @Override + public boolean allowMinDifficultyBlocks() { + return false; + } + */ + + @Override + public String getPaymentProtocolId() { + // TODO: CHANGE THIS (comment from Dogecoin) + return ID_NMC_MAINNET; + } + + @Override + public boolean isTestNet() { + return false; + } +}