From fbc14c1040510176cf61e197eedd800362bc29ef Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 28 Feb 2013 19:39:05 +0100 Subject: [PATCH] Make NetworkParameters singleton and (mostly) immutable. Resolves issue 51. --- .../bitcoin/core/NetworkParameters.java | 300 +++++++++--------- 1 file changed, 146 insertions(+), 154 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java index da7f8e6d4..40cb6d269 100644 --- a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java +++ b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java @@ -28,10 +28,8 @@ import java.util.Map; import static com.google.bitcoin.core.Utils.COIN; import static com.google.common.base.Preconditions.checkState; -// TODO: Refactor this after we stop supporting serialization compatibility to use subclasses and singletons. - /** - * NetworkParameters contains the data needed for working with an instantiation of a Bitcoin chain.

+ *

NetworkParameters contains the data needed for working with an instantiation of a Bitcoin chain.

* * Currently there are only two, the production chain and the test chain. But in future as Bitcoin * evolves there may be more. You can create your own as long as they don't conflict. @@ -58,73 +56,75 @@ public class NetworkParameters implements Serializable { // TODO: Seed nodes should be here as well. + // TODO: Replace with getters and then finish making all these fields final. + /** - * Genesis block for this chain.

+ *

Genesis block for this chain.

* - * The first block in every chain is a well known constant shared between all BitCoin implemenetations. For a + *

The first block in every chain is a well known constant shared between all BitCoin implemenetations. For a * block to be valid, it must be eventually possible to work backwards to the genesis block by following the - * prevBlockHash pointers in the block headers.

+ * prevBlockHash pointers in the block headers.

* - * The genesis blocks for both test and prod networks contain the timestamp of when they were created, + *

The genesis blocks for both test and prod networks contain the timestamp of when they were created, * and a message in the coinbase transaction. It says, "The Times 03/Jan/2009 Chancellor on brink of second - * bailout for banks". + * bailout for banks".

*/ - public Block genesisBlock; + public final Block genesisBlock; /** What the easiest allowable proof of work should be. */ - public BigInteger proofOfWorkLimit; + public /*final*/ BigInteger proofOfWorkLimit; /** Default TCP port on which to connect to nodes. */ - public int port; + public final int port; /** The header bytes that identify the start of a packet on this network. */ - public long packetMagic; + public final long packetMagic; /** * First byte of a base58 encoded address. See {@link Address}. This is the same as acceptableAddressCodes[0] and * is the one used for "normal" addresses. Other types of address may be encountered with version codes found in * the acceptableAddressCodes array. */ - public int addressHeader; + public final int addressHeader; /** First byte of a base58 encoded dumped private key. See {@link DumpedPrivateKey}. */ - public int dumpedPrivateKeyHeader; + public final int dumpedPrivateKeyHeader; /** How many blocks pass between difficulty adjustment periods. Bitcoin standardises this to be 2015. */ - public int interval; + public /*final*/ int interval; /** * How much time in seconds is supposed to pass between "interval" blocks. If the actual elapsed time is * significantly different from this value, the network difficulty formula will produce a different value. Both * test and production Bitcoin networks use 2 weeks (1209600 seconds). */ - public int targetTimespan; + public final int targetTimespan; /** * The key used to sign {@link AlertMessage}s. You can use {@link ECKey#verify(byte[], byte[], byte[])} to verify * signatures using it. */ - public byte[] alertSigningKey; + public /*final*/ byte[] alertSigningKey; /** * See getId(). This may be null for old deserialized wallets. In that case we derive it heuristically * by looking at the port number. */ - private String id; + private final String id; /** * The depth of blocks required for a coinbase transaction to be spendable. */ - private int spendableCoinbaseDepth; + private final int spendableCoinbaseDepth; /** * Returns the number of blocks between subsidy decreases */ - private int subsidyDecreaseBlockCount; + private final int subsidyDecreaseBlockCount; /** * If we are running in testnet-in-a-box mode, we allow connections to nodes with 0 non-genesis blocks */ - boolean allowEmptyPeerChains; + final boolean allowEmptyPeerChains; /** * The version codes that prefix addresses which are acceptable on this network. Although Satoshi intended these to * be used for "versioning", in fact they are today used to discriminate what kind of data is contained in the * address and to prevent accidentally sending coins across chains which would destroy them. */ - public int[] acceptableAddressCodes; + public final int[] acceptableAddressCodes; /** @@ -134,6 +134,99 @@ public class NetworkParameters implements Serializable { */ public Map checkpoints = new HashMap(); + private NetworkParameters(int type) { + alertSigningKey = SATOSHI_KEY; + genesisBlock = createGenesis(this); + if (type == 0) { + // Production. + interval = INTERVAL; + targetTimespan = TARGET_TIMESPAN; + proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); + acceptableAddressCodes = new int[] { 0 }; + dumpedPrivateKeyHeader = 128; + addressHeader = 0; + port = 8333; + packetMagic = 0xf9beb4d9L; + genesisBlock.setDifficultyTarget(0x1d00ffffL); + genesisBlock.setTime(1231006505L); + genesisBlock.setNonce(2083236893); + id = ID_PRODNET; + subsidyDecreaseBlockCount = 210000; + allowEmptyPeerChains = false; + spendableCoinbaseDepth = 100; + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), + genesisHash); + + // 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(91722, new Sha256Hash("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); + checkpoints.put(91812, new Sha256Hash("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); + checkpoints.put(91842, new Sha256Hash("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); + checkpoints.put(91880, new Sha256Hash("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); + checkpoints.put(200000, new Sha256Hash("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); + } else if (type == 3) { + // Testnet3 + id = ID_TESTNET; + // Genesis hash is 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943 + packetMagic = 0x0b110907; + interval = INTERVAL; + targetTimespan = TARGET_TIMESPAN; + proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); + port = 18333; + addressHeader = 111; + acceptableAddressCodes = new int[] { 111 }; + dumpedPrivateKeyHeader = 239; + genesisBlock.setTime(1296688602L); + genesisBlock.setDifficultyTarget(0x1d00ffffL); + genesisBlock.setNonce(414098458); + allowEmptyPeerChains = true; + spendableCoinbaseDepth = 100; + subsidyDecreaseBlockCount = 210000; + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"), + genesisHash); + } else if (type == 2) { + id = ID_TESTNET; + packetMagic = 0xfabfb5daL; + port = 18333; + addressHeader = 111; + interval = INTERVAL; + targetTimespan = TARGET_TIMESPAN; + proofOfWorkLimit = Utils.decodeCompactBits(0x1d0fffffL); + acceptableAddressCodes = new int[] { 111 }; + dumpedPrivateKeyHeader = 239; + genesisBlock.setTime(1296688602L); + genesisBlock.setDifficultyTarget(0x1d07fff8L); + genesisBlock.setNonce(384568319); + allowEmptyPeerChains = false; + spendableCoinbaseDepth = 100; + subsidyDecreaseBlockCount = 210000; + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"), + genesisHash); + } else if (type == -1) { + id = ID_UNITTESTNET; + packetMagic = 0x0b110907; + addressHeader = 111; + proofOfWorkLimit = new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + genesisBlock.setTime(System.currentTimeMillis() / 1000); + genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET); + genesisBlock.solve(); + port = 18333; + interval = 10; + dumpedPrivateKeyHeader = 239; + allowEmptyPeerChains = false; + targetTimespan = 200000000; // 6 years. Just a very big number. + spendableCoinbaseDepth = 5; + acceptableAddressCodes = new int[] { 111 }; + subsidyDecreaseBlockCount = 100; + } else { + throw new RuntimeException(); + } + } private static Block createGenesis(NetworkParameters n) { Block genesisBlock = new Block(n); @@ -172,138 +265,51 @@ public class NetworkParameters implements Serializable { /** * The maximum money to be generated */ - public final BigInteger MAX_MONEY = new BigInteger("21000000", 10).multiply(COIN); - - /** Sets up the given Networkparemets with testnet3 values. */ - private static NetworkParameters createTestNet3(NetworkParameters n) { - // Genesis hash is 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943 - n.proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); - n.packetMagic = 0x0b110907; - n.port = 18333; - n.addressHeader = 111; - n.acceptableAddressCodes = new int[] { 111 }; - n.dumpedPrivateKeyHeader = 239; - n.interval = INTERVAL; - n.targetTimespan = TARGET_TIMESPAN; - n.alertSigningKey = SATOSHI_KEY; - n.genesisBlock = createGenesis(n); - n.genesisBlock.setTime(1296688602L); - n.genesisBlock.setDifficultyTarget(0x1d00ffffL); - n.genesisBlock.setNonce(414098458); - n.setSpendableCoinbaseDepth(100); - n.setSubsidyDecreaseBlockCount(210000); - n.id = ID_TESTNET; - String genesisHash = n.genesisBlock.getHashAsString(); - checkState(genesisHash.equals("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"), - genesisHash); - return n; - } - - /** Sets up the given NetworkParameters with testnet2 values. Don't use! */ - private static NetworkParameters createOldTestNet(NetworkParameters n) { - // Genesis hash is 0000000224b1593e3ff16a0e3b61285bbc393a39f78c8aa48c456142671f7110 - n.proofOfWorkLimit = Utils.decodeCompactBits(0x1d0fffffL); - n.packetMagic = 0xfabfb5daL; - n.port = 18333; - n.addressHeader = 111; - n.acceptableAddressCodes = new int[] { 111 }; - n.dumpedPrivateKeyHeader = 239; - n.interval = INTERVAL; - n.targetTimespan = TARGET_TIMESPAN; - n.alertSigningKey = SATOSHI_KEY; - n.genesisBlock = createGenesis(n); - n.genesisBlock.setTime(1296688602L); - n.genesisBlock.setDifficultyTarget(0x1d07fff8L); - n.genesisBlock.setNonce(384568319); - n.setSpendableCoinbaseDepth(100); - n.setSubsidyDecreaseBlockCount(210000); - n.id = ID_TESTNET; - n.allowEmptyPeerChains = false; - String genesisHash = n.genesisBlock.getHashAsString(); - checkState(genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"), - genesisHash); - return n; - } + public static final BigInteger MAX_MONEY = new BigInteger("21000000", 10).multiply(COIN); /** Returns whatever the latest testNet parameters are. Use this rather than the versioned equivalents. */ public static NetworkParameters testNet() { return testNet3(); } - public static NetworkParameters testNet2() { - NetworkParameters n = new NetworkParameters(); - return createOldTestNet(n); + private static NetworkParameters tn2; + public synchronized static NetworkParameters testNet2() { + if (tn2 == null) { + tn2 = new NetworkParameters(2); + } + return tn2; } - public static NetworkParameters testNet3() { - NetworkParameters n = new NetworkParameters(); - return createTestNet3(n); + private static NetworkParameters tn3; + public synchronized static NetworkParameters testNet3() { + if (tn3 == null) { + tn3 = new NetworkParameters(3); + } + return tn3; } - /** The primary BitCoin chain created by Satoshi. */ - public static NetworkParameters prodNet() { - NetworkParameters n = new NetworkParameters(); - n.proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); - n.port = 8333; - n.packetMagic = 0xf9beb4d9L; - n.addressHeader = 0; - n.acceptableAddressCodes = new int[] { 0 }; - n.dumpedPrivateKeyHeader = 128; - n.interval = INTERVAL; - n.targetTimespan = TARGET_TIMESPAN; - n.alertSigningKey = SATOSHI_KEY; - n.genesisBlock = createGenesis(n); - n.genesisBlock.setDifficultyTarget(0x1d00ffffL); - n.genesisBlock.setTime(1231006505L); - n.genesisBlock.setNonce(2083236893); - n.setSpendableCoinbaseDepth(100); - n.setSubsidyDecreaseBlockCount(210000); - n.id = ID_PRODNET; - n.allowEmptyPeerChains = false; - String genesisHash = n.genesisBlock.getHashAsString(); - checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), - genesisHash); - - // 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. - n.checkpoints.put(new Integer(91722), new Sha256Hash("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); - n.checkpoints.put(new Integer(91812), new Sha256Hash("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); - n.checkpoints.put(new Integer(91842), new Sha256Hash("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); - n.checkpoints.put(new Integer(91880), new Sha256Hash("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); - n.checkpoints.put(new Integer(200000), new Sha256Hash("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); - return n; + private static NetworkParameters pn; + /** The primary Bitcoin chain created by Satoshi. */ + public synchronized static NetworkParameters prodNet() { + if (pn == null) { + pn = new NetworkParameters(0); + } + return pn; } + private static NetworkParameters ut; /** Returns a testnet params modified to allow any difficulty target. */ - public static NetworkParameters unitTests() { - NetworkParameters n = new NetworkParameters(); - n = createTestNet3(n); - n.proofOfWorkLimit = new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); - n.genesisBlock.setTime(System.currentTimeMillis() / 1000); - n.genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET); - n.genesisBlock.solve(); - n.interval = 10; - n.targetTimespan = 200000000; // 6 years. Just a very big number. - n.setSpendableCoinbaseDepth(5); - n.setSubsidyDecreaseBlockCount(100); - n.id = "com.google.bitcoin.unittest"; - return n; + public synchronized static NetworkParameters unitTests() { + if (ut == null) { + ut = new NetworkParameters(-1); + } + return ut; } /** - * A java package style string acting as unique ID for these parameters + * A Java package style string acting as unique ID for these parameters */ public String getId() { - if (id == null) { - // Migrate from old serialized wallets which lack the ID field. This code can eventually be deleted. - if (port == 8333) { - id = ID_PRODNET; - } else if (port == 18333) { - id = ID_TESTNET; - } - } return id; } @@ -336,36 +342,22 @@ public class NetworkParameters implements Serializable { return spendableCoinbaseDepth; } - public void setSpendableCoinbaseDepth(int coinbaseDepth) { - this.spendableCoinbaseDepth = coinbaseDepth; - } - /** * Returns true if the block height is either not a checkpoint, or is a checkpoint and the hash matches. */ public boolean passesCheckpoint(int height, Sha256Hash hash) { - Sha256Hash checkpointHash = checkpoints.get(Integer.valueOf(height)); - if (checkpointHash != null) - return checkpointHash.equals(hash); - return true; + Sha256Hash checkpointHash = checkpoints.get(height); + return checkpointHash == null || checkpointHash.equals(hash); } /** * Returns true if the given height has a recorded checkpoint. - * @param height - * @return */ public boolean isCheckpoint(int height) { - Sha256Hash checkpointHash = checkpoints.get(Integer.valueOf(height)); - if (checkpointHash != null) - return true; - return false; + Sha256Hash checkpointHash = checkpoints.get(height); + return checkpointHash != null; } - public void setSubsidyDecreaseBlockCount(int subsidyDecreaseBlockCount) { - this.subsidyDecreaseBlockCount = subsidyDecreaseBlockCount; - } - public int getSubsidyDecreaseBlockCount() { return subsidyDecreaseBlockCount; }