diff --git a/src/main/java/org/bitcoinj/core/AltcoinBlock.java b/src/main/java/org/bitcoinj/core/AltcoinBlock.java index a77413cd..052bff90 100644 --- a/src/main/java/org/bitcoinj/core/AltcoinBlock.java +++ b/src/main/java/org/bitcoinj/core/AltcoinBlock.java @@ -26,12 +26,14 @@ import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.security.GeneralSecurityException; +import java.util.BitSet; import java.util.List; import org.libdohj.core.ScryptHash; import static org.libdohj.core.Utils.scryptDigest; import static org.bitcoinj.core.Utils.reverseBytes; +import org.libdohj.core.AuxPoWNetworkParameters; /** *
A block is a group of transactions, and is one of the fundamental data structures of the Bitcoin system. @@ -44,17 +46,18 @@ import static org.bitcoinj.core.Utils.reverseBytes; * specifically using {@link Peer#getBlock(Sha256Hash)}, or grab one from a downloaded {@link BlockChain}. */ public class AltcoinBlock extends org.bitcoinj.core.Block { - /** Bit used to indicate that a block contains an AuxPoW section, where the network supports AuxPoW */ - public static final int BLOCK_VERSION_CHAIN_START = (1 << 16); - public static final int BLOCK_VERSION_CHAIN_END = (1 << 30); - public static final int BLOCK_VERSION_AUXPOW = (1 << 8); - private boolean auxpowParsed = false; private boolean auxpowBytesValid = false; /** AuxPoW header element, if applicable. */ @Nullable private AuxPoW auxpow; + /** + * Whether the chain this block belongs to support AuxPoW, used to avoid + * repeated instanceof checks. Initialised in parseTransactions() + */ + private boolean auxpowChain = false; + private ScryptHash scryptHash; /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */ @@ -115,7 +118,6 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { } public AuxPoW getAuxPoW() { - // TODO: maybeParseAuxPoW(); return this.auxpow; } @@ -136,14 +138,65 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { return getScryptHash().toString(); } + /** + * Get the chain ID (upper 16 bits) from an AuxPoW version number. + */ + public static long getChainID(final long rawVersion) { + return rawVersion >> 16; + } + + /** + * Return chain ID from block version of an AuxPoW-enabled chain. + */ + public long getChainID() { + return getChainID(this.getRawVersion()); + } + + /** + * Return flags from block version of an AuxPoW-enabled chain. + * + * @return flags as a bitset. + */ + public BitSet getVersionFlags() { + return BitSet.valueOf(new long[] {(this.getRawVersion() & 0xff00) >> 8}); + } + + /** + * Return block version without applying any filtering (i.e. for AuxPoW blocks + * which structure version differently to pack in additional data). + */ + public final long getRawVersion() { + return super.getVersion(); + } + + /** + * Get the base version (i.e. Bitcoin-like version number) out of a packed + * AuxPoW version number (i.e. one that contains chain ID and feature flags). + */ + public static long getBaseVersion(final long rawVersion) { + return rawVersion & 0xff; + } + + @Override + public long getVersion() { + // TODO: Can we cache the individual parts on parse? + if (this.params instanceof AltcoinNetworkParameters) { + // AuxPoW networks use the higher block version bits for flags and + // chain ID. + return getBaseVersion(super.getVersion()); + } else { + return super.getVersion(); + } + } + protected void parseAuxPoW() throws ProtocolException { if (this.auxpowParsed) return; this.auxpow = null; - if (this.params instanceof AltcoinNetworkParameters) { - final AltcoinNetworkParameters altcoinParams = (AltcoinNetworkParameters)this.params; - if (altcoinParams.isAuxPoWBlockVersion(this.getVersion())) { + if (this.auxpowChain) { + final AuxPoWNetworkParameters auxpowParams = (AuxPoWNetworkParameters)this.params; + if (auxpowParams.isAuxPoWBlockVersion(this.getRawVersion())) { // The following is used in dogecoinj, but I don't think we necessarily need it // payload.length >= 160) { // We have at least 2 headers in an Aux block. Workaround for StoredBlocks this.auxpow = new AuxPoW(params, payload, cursor, this, serializer); @@ -157,6 +210,7 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { @Override protected void parseTransactions(final int offset) { + this.auxpowChain = params instanceof AuxPoWNetworkParameters; parseAuxPoW(); if (null != this.auxpow) { super.parseTransactions(offset + auxpow.getMessageSize()); @@ -176,7 +230,7 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { /** Returns a copy of the block, but without any transactions. */ @Override public Block cloneAsHeader() { - AltcoinBlock block = new AltcoinBlock(params, getVersion()); + AltcoinBlock block = new AltcoinBlock(params, getRawVersion()); super.copyBitcoinHeaderTo(block); block.auxpow = auxpow; return block; @@ -187,8 +241,8 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { if (params instanceof AltcoinNetworkParameters) { BigInteger target = getDifficultyTargetAsInteger(); - final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)auxpow; - if (altParams.isAuxPoWBlockVersion(getVersion()) && null != auxpow) { + final AuxPoWNetworkParameters altParams = (AuxPoWNetworkParameters)auxpow; + if (altParams.isAuxPoWBlockVersion(getRawVersion()) && null != auxpow) { return auxpow.checkProofOfWork(this.getHash(), target, throwException); } diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index dde0f6f6..be770102 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -18,7 +18,7 @@ package org.bitcoinj.core; -import org.libdohj.core.AltcoinNetworkParameters; +import org.libdohj.core.AuxPoWNetworkParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -239,14 +239,14 @@ public class AuxPoW extends ChildMessage { */ protected boolean checkProofOfWork(Sha256Hash hashAuxBlock, BigInteger target, boolean throwException) throws VerificationException { - if (!(params instanceof AltcoinNetworkParameters)) { + if (!(params instanceof AuxPoWNetworkParameters)) { if (throwException) { // Should be impossible - throw new VerificationException("Network parameters are not an instance of AltcoinNetworkParameters, AuxPoW support is not available."); + throw new VerificationException("Network parameters are not an instance of AuxPoWNetworkParameters, AuxPoW support is not available."); } return false; } - final AltcoinNetworkParameters altcoinParams = (AltcoinNetworkParameters) params; + final AuxPoWNetworkParameters altcoinParams = (AuxPoWNetworkParameters) params; if (0 != this.getCoinbaseBranch().getIndex()) { if (throwException) { @@ -257,7 +257,7 @@ public class AuxPoW extends ChildMessage { } if (!altcoinParams.isTestNet() - && getChainID(parentBlockHeader) == altcoinParams.getChainID()) { + && parentBlockHeader.getChainID() == altcoinParams.getChainID()) { if (throwException) { throw new VerificationException("Aux POW parent has our chain ID"); } @@ -362,7 +362,7 @@ public class AuxPoW extends ChildMessage { // for the same slot. long rand = nonce; rand = rand * 1103515245 + 12345; - rand += ((AltcoinNetworkParameters) params).getChainID(); + rand += ((AuxPoWNetworkParameters) params).getChainID(); rand = rand * 1103515245 + 12345; if (getChainMerkleBranch().getIndex() != (rand % branchSize)) { @@ -407,13 +407,6 @@ public class AuxPoW extends ChildMessage { return true; } - /** - * Get the chain ID from a block header. - */ - public static long getChainID(final Block blockHeader) { - return blockHeader.getVersion() / AltcoinBlock.BLOCK_VERSION_CHAIN_START; - } - /** * Set the merkle branch used to connect the coinbase transaction to the * parent block header. diff --git a/src/main/java/org/bitcoinj/core/ConvertAddress.java b/src/main/java/org/bitcoinj/core/ConvertAddress.java index afa2a956..5c07bdd7 100644 --- a/src/main/java/org/bitcoinj/core/ConvertAddress.java +++ b/src/main/java/org/bitcoinj/core/ConvertAddress.java @@ -16,7 +16,7 @@ public class ConvertAddress { public static void main(final String[] argv) throws AddressFormatException { final NetworkParameters mainParams = MainNetParams.get(); final NetworkParameters dogeParams = DogecoinMainNetParams.get(); - final Address address = Address.fromBase58(mainParams, "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"); + final Address address = Address.fromBase58(mainParams, "175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"); final Address newAddress = new Address(dogeParams, 30, address.getHash160()); System.out.println(newAddress.toBase58()); diff --git a/src/main/java/org/libdohj/core/AltcoinNetworkParameters.java b/src/main/java/org/libdohj/core/AltcoinNetworkParameters.java index 1539d479..384cb0f1 100644 --- a/src/main/java/org/libdohj/core/AltcoinNetworkParameters.java +++ b/src/main/java/org/libdohj/core/AltcoinNetworkParameters.java @@ -20,14 +20,9 @@ import org.bitcoinj.core.Sha256Hash; /** * - * @author jrn + * @author Ross Nicoll */ public interface AltcoinNetworkParameters { - - boolean isAuxPoWBlockVersion(long version); - - int getChainID(); - /** * Get the hash for the given block, for comparing against target difficulty. * This provides an extension hook for networks which use a hash other than diff --git a/src/main/java/org/libdohj/core/AuxPoWNetworkParameters.java b/src/main/java/org/libdohj/core/AuxPoWNetworkParameters.java new file mode 100644 index 00000000..cf1f5125 --- /dev/null +++ b/src/main/java/org/libdohj/core/AuxPoWNetworkParameters.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Ross Nicoll + * + * 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.core; + +/** + * + * @author Ross Nicoll + */ +public interface AuxPoWNetworkParameters extends AltcoinNetworkParameters { + + boolean isAuxPoWBlockVersion(long version); + + int getChainID(); +} diff --git a/src/main/java/org/libdohj/params/AbstractDogecoinParams.java b/src/main/java/org/libdohj/params/AbstractDogecoinParams.java index 15b5440e..50ef20ba 100644 --- a/src/main/java/org/libdohj/params/AbstractDogecoinParams.java +++ b/src/main/java/org/libdohj/params/AbstractDogecoinParams.java @@ -35,18 +35,19 @@ import org.bitcoinj.utils.MonetaryFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.libdohj.core.AltcoinSerializer; 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; /** * Parameters for the main Dogecoin production network on which people trade goods and services. */ -public abstract class AbstractDogecoinParams extends NetworkParameters implements AltcoinNetworkParameters { +public abstract class AbstractDogecoinParams extends NetworkParameters implements AuxPoWNetworkParameters { /** Standard format for the DOGE denomination. */ public static final MonetaryFormat DOGE; /** Standard format for the mDOGE denomination. */ @@ -69,9 +70,8 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement /** Currency code for base 1/100,000,000 Dogecoin. */ public static final String CODE_KOINU = "Koinu"; - public static final int BLOCK_VERSION_DEFAULT = 0x00000002; - public static final int BLOCK_VERSION_AUXPOW = 0x00620002; - public static final int BLOCK_VERSION_FLAG_AUXPOW = 0x00000100; + private static final int BLOCK_MIN_VERSION_AUXPOW = 0x00620002; + private static final int BLOCK_VERSION_FLAG_AUXPOW = 0x00000100; static { DOGE = MonetaryFormat.BTC.noCode() @@ -311,7 +311,7 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement @Override public boolean isAuxPoWBlockVersion(long version) { - return version >= BLOCK_VERSION_AUXPOW + return version >= BLOCK_MIN_VERSION_AUXPOW && (version & BLOCK_VERSION_FLAG_AUXPOW) > 0; } } diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java index 28366651..0aa08a09 100644 --- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java +++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java @@ -2,17 +2,20 @@ package org.bitcoinj.core; import java.util.Arrays; import java.util.Collections; + import org.libdohj.core.AltcoinSerializer; +import org.libdohj.core.AuxPoWNetworkParameters; import org.libdohj.params.DogecoinMainNetParams; -import org.junit.Test; import static org.bitcoinj.core.Util.getBytes; import static org.bitcoinj.core.Utils.reverseBytes; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.junit.Before; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; /** @@ -97,7 +100,9 @@ public class AuxPoWTest { byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin")); AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); + assertEquals(98, block.getChainID()); final AuxPoW auxpow = block.getAuxPoW(); + assertNotNull(auxpow); auxpow.setParentBlockHeader((AltcoinBlock)block.cloneAsHeader()); expectedEx.expect(org.bitcoinj.core.VerificationException.class); expectedEx.expectMessage("Aux POW parent has our chain ID"); diff --git a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java index 1b155bbd..b1ee4bb9 100644 --- a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java +++ b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; @@ -27,6 +28,24 @@ public class DogecoinBlockTest { Context context = new Context(params); } + @Test + public void shouldExtractChainID() { + final long baseVersion = 2; + final long flags = 1; + final long chainID = 98; + final long auxpowVersion = (chainID << 16) | (flags << 8) | baseVersion; + assertEquals(chainID, AltcoinBlock.getChainID(auxpowVersion)); + } + + @Test + public void shouldExtractBaseVersion() { + final long baseVersion = 2; + final long flags = 1; + final long chainID = 98; + final long auxpowVersion = (chainID << 16) | (flags << 8) | baseVersion; + assertEquals(baseVersion, AltcoinBlock.getBaseVersion(auxpowVersion)); + } + @Test public void shouldParseBlock1() throws IOException { byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block1.bin")); @@ -65,6 +84,12 @@ public class DogecoinBlockTest { final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); assertEquals("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", block.getHashAsString()); assertEquals(0, block.getNonce()); + + // Check block version values + assertEquals(2, block.getVersion()); + assertEquals(98, block.getChainID()); + assertTrue(block.getVersionFlags().get(0)); + final AuxPoW auxpow = block.getAuxPoW(); assertNotNull(auxpow); final Transaction auxpowCoinbase = auxpow.getCoinbase(); @@ -75,18 +100,18 @@ public class DogecoinBlockTest { final MerkleBranch blockchainMerkleBranch = auxpow.getChainMerkleBranch(); Sha256Hash[] expected = new Sha256Hash[] { - new Sha256Hash("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"), - new Sha256Hash("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"), - new Sha256Hash("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103") + Sha256Hash.wrap("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"), + Sha256Hash.wrap("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"), + Sha256Hash.wrap("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103") }; assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.size()])); final MerkleBranch coinbaseMerkleBranch = auxpow.getCoinbaseBranch(); expected = new Sha256Hash[] { - new Sha256Hash("cd3947cd5a0c26fde01b05a3aa3d7a38717be6ae11d27239365024db36a679a9"), - new Sha256Hash("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"), - new Sha256Hash("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a") + Sha256Hash.wrap("cd3947cd5a0c26fde01b05a3aa3d7a38717be6ae11d27239365024db36a679a9"), + Sha256Hash.wrap("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"), + Sha256Hash.wrap("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a") }; assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.size()]));