diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/AbstractBlockChain.java b/core/src/main/java/com/dogecoin/dogecoinj/core/AbstractBlockChain.java index 2932c557..268aa200 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/AbstractBlockChain.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/AbstractBlockChain.java @@ -834,6 +834,7 @@ public abstract class AbstractBlockChain { // February 16th 2012 private static final Date testnetDiffDate = new Date(1329264000000L); + private static final int testNetRetargetFix = 157500; /** * Throws an exception if the blocks difficulty is not correct. @@ -841,9 +842,25 @@ public abstract class AbstractBlockChain { private void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) throws BlockStoreException, VerificationException { checkState(lock.isHeldByCurrentThread()); Block prev = storedPrev.getHeader(); + + boolean newDiffAlgo = storedPrev.getHeight() + 1 >= params.getDiffChangeTarget(); + int retargetInterval = params.getInterval(); + int retargetTimespan = params.getTargetTimespan(); + if (newDiffAlgo) + { + retargetInterval = params.getNewInterval(); + retargetTimespan = params.getNewTargetTimespan(); + } + + if (params.getId().equals(NetworkParameters.ID_TESTNET) + && storedPrev.getHeight() >= testNetRetargetFix + && nextBlock.getTimeSeconds() > storedPrev.getHeader().getTimeSeconds() + retargetTimespan * 2 ) { + checkTestnetDifficulty(storedPrev, prev, nextBlock); + return; + } // Is this supposed to be a difficulty transition point? - if ((storedPrev.getHeight() + 1) % params.getInterval() != 0) { + if ((storedPrev.getHeight() + 1) % retargetInterval != 0) { // TODO: Refactor this hack after 0.5 is released and we stop supporting deserialization compatibility. // This should be a method of the NetworkParameters, which should in turn be using singletons and a subclass @@ -865,7 +882,11 @@ public abstract class AbstractBlockChain { // two weeks after the initial block chain download. long now = System.currentTimeMillis(); StoredBlock cursor = blockStore.get(prev.getHash()); - for (int i = 0; i < params.getInterval() - 1; i++) { + 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( @@ -873,18 +894,50 @@ public abstract class AbstractBlockChain { } cursor = blockStore.get(cursor.getHeader().getPrevBlockHash()); } + + //We used checkpoints... + if(cursor == null) + { + log.debug("Difficulty transition: Hit checkpoint!"); + return; + } + long elapsed = System.currentTimeMillis() - now; if (elapsed > 50) log.info("Difficulty transition traversal took {}msec", elapsed); Block blockIntervalAgo = cursor.getHeader(); int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds()); + final int targetTimespan = retargetTimespan; + + if (newDiffAlgo) + { + timespan = retargetTimespan + (timespan - retargetTimespan)/8; + if (timespan < (retargetTimespan - (retargetTimespan/4)) ) timespan = (retargetTimespan - (retargetTimespan/4)); + if (timespan > (retargetTimespan + (retargetTimespan/2)) ) timespan = (retargetTimespan + (retargetTimespan/2)); + } // Limit the adjustment step. - final int targetTimespan = params.getTargetTimespan(); - if (timespan < targetTimespan / 4) - timespan = targetTimespan / 4; - if (timespan > targetTimespan * 4) - timespan = targetTimespan * 4; + else if (storedPrev.getHeight()+1 > 10000) + { + if (timespan < targetTimespan / 4) + timespan = targetTimespan / 4; + if (timespan > targetTimespan * 4) + timespan = targetTimespan * 4; + } + else if (storedPrev.getHeight()+1 > 5000) + { + if (timespan < targetTimespan / 8) + timespan = targetTimespan / 8; + if (timespan > targetTimespan * 4) + timespan = targetTimespan * 4; + } + else + { + if (timespan < targetTimespan / 16) + timespan = targetTimespan / 16; + if (timespan > targetTimespan * 4) + timespan = targetTimespan * 4; + } BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget()); newTarget = newTarget.multiply(BigInteger.valueOf(timespan)); diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/AuxPoWMessage.java b/core/src/main/java/com/dogecoin/dogecoinj/core/AuxPoWMessage.java new file mode 100644 index 00000000..cdd32702 --- /dev/null +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/AuxPoWMessage.java @@ -0,0 +1,141 @@ +/* + * Copyright 2011 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 com.dogecoin.dogecoinj.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This is the code to deserialize the AuxPoW header data + */ +public class AuxPoWMessage extends Message { + private static final Logger log = LoggerFactory.getLogger(AuxPoWMessage.class); + + private AuxHeader header; + + public AuxPoWMessage(byte[] payload, int cursor) throws ProtocolException { + this.payload = payload; + this.cursor = cursor; + this.header = new AuxHeader(); + } + + @Override + void parse() throws ProtocolException { + header.parentCoinbaseVerion = readUint32(); + header.parentCoinbaseTxInCount = readVarInt(); + header.parentCointbasePrevOut = readBytes(36); // Always the same on coinbase + header.parentCoinbaseInScriptLength = readVarInt(); + header.parentCoinbaseInScript = readBytes((int) header.parentCoinbaseInScriptLength); // Script length is limited so this cast should be fine. + header.parentCoinBaseSequenceNumber = readUint32(); + header.parentCoinbaseTxOutCount = readVarInt(); + header.parentCoinbaseOuts = new ArrayList(); + for (int i = 0; i < header.parentCoinbaseTxOutCount; i++) { + AuxCoinbaseOut out = new AuxCoinbaseOut(); + out.amount = readInt64(); + out.scriptLength = readVarInt(); + out.script = readBytes((int) out.scriptLength); // Script length is limited so this cast should be fine. + header.parentCoinbaseOuts.add(out); + } + header.parentCoinbaseLockTime = readUint32(); + + header.parentBlockHeaderHash = readHash(); + header.numOfCoinbaseLinks = readVarInt(); + header.coinbaseLinks = new ArrayList(); + for (int i = 0; i < header.numOfCoinbaseLinks; i++) { + header.coinbaseLinks.add(readHash()); + } + header.coinbaseBranchBitmask = readUint32(); + + header.numOfAuxChainLinks = readVarInt(); + header.auxChainLinks = new ArrayList(); + for (int i = 0; i < header.numOfAuxChainLinks; i++) { + header.auxChainLinks.add(readHash()); + } + header.auxChainBranchBitmask = readUint32(); + + header.parentBlockVersion = readUint32(); + header.parentBlockPrev = readHash(); + header.parentBlockMerkleRoot = readHash(); + header.parentBlockTime = readUint32(); + header.parentBlockBits = readUint32(); + header.parentBlockNonce = readUint32(); + } + + @Override + protected void parseLite() throws ProtocolException { + // noop + } + + public byte[] constructParentHeader() { + + ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(Block.HEADER_SIZE); + try { + Utils.uint32ToByteStreamLE(header.parentBlockVersion, stream); + stream.write(Utils.reverseBytes(header.parentBlockPrev.getBytes())); + stream.write(Utils.reverseBytes(header.parentBlockMerkleRoot.getBytes())); + Utils.uint32ToByteStreamLE(header.parentBlockTime, stream); + Utils.uint32ToByteStreamLE(header.parentBlockBits, stream); + Utils.uint32ToByteStreamLE(header.parentBlockNonce, stream); + } catch (IOException e) { + throw new RuntimeException(); // Can't actually happen + } + return stream.toByteArray(); + } + + public class AuxHeader { + + // Parent coinbase + public long parentCoinbaseVerion; + public long parentCoinbaseTxInCount; + public byte[] parentCointbasePrevOut; + public long parentCoinbaseInScriptLength; + public byte[] parentCoinbaseInScript; + public long parentCoinBaseSequenceNumber; + public long parentCoinbaseTxOutCount; + public ArrayList parentCoinbaseOuts; + public long parentCoinbaseLockTime; + + // Coinbase link + public Sha256Hash parentBlockHeaderHash; + public long numOfCoinbaseLinks; + public ArrayList coinbaseLinks; + public long coinbaseBranchBitmask; + + // Aux chanin link + public long numOfAuxChainLinks; + public ArrayList auxChainLinks; + public long auxChainBranchBitmask; + + // Parent block header + public long parentBlockVersion; + public Sha256Hash parentBlockPrev; + public Sha256Hash parentBlockMerkleRoot; + public long parentBlockTime; + public long parentBlockBits; + public long parentBlockNonce; + } + + public class AuxCoinbaseOut { + public long amount; + public long scriptLength; + public byte[] script; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Block.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Block.java index 5127cfbd..f42b99e1 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Block.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Block.java @@ -38,6 +38,7 @@ import java.util.List; import static com.dogecoin.dogecoinj.core.Coin.FIFTY_COINS; import static com.dogecoin.dogecoinj.core.Utils.doubleDigest; import static com.dogecoin.dogecoinj.core.Utils.doubleDigestTwoBuffers; +import static com.dogecoin.dogecoinj.core.Utils.scryptDigest; /** *

A block is a group of transactions, and is one of the fundamental data structures of the Bitcoin system. @@ -74,6 +75,10 @@ public class Block extends Message { /** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */ public static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL; + public static final int BLOCK_VERSION_DEFAULT = 0x00000002; + public static final int BLOCK_VERSION_AUXPOW = 0x00620002; + public static final int BLOCK_VERSION_AUXPOW_AUXBLOCK = 0x00620102; + // Fields defined as part of the protocol format. private long version; private Sha256Hash prevBlockHash; @@ -82,11 +87,15 @@ public class Block extends Message { private long difficultyTarget; // "nBits" private long nonce; + // The parent block header for AuxPoW blocks + private Block parentBlock; + /** If null, it means this object holds only the headers. */ List transactions; /** Stores the hash of the block. If null, getHash() will recalculate it. */ private transient Sha256Hash hash; + private transient Sha256Hash scryptHash; private transient boolean headerParsed; private transient boolean transactionsParsed; @@ -116,6 +125,12 @@ public class Block extends Message { super(params, payloadBytes, 0, false, false, payloadBytes.length); } + /** Constructs a block object from the Bitcoin wire format. */ + public Block(NetworkParameters params, byte[] payloadBytes, Block parentBlock) throws ProtocolException { + super(params, payloadBytes, 0, false, false, payloadBytes.length); + this.parentBlock = parentBlock; + } + /** * Contruct a block object from the Bitcoin wire format. * @param params NetworkParameters object. @@ -131,7 +146,11 @@ public class Block extends Message { throws ProtocolException { super(params, payloadBytes, 0, parseLazy, parseRetain, length); } - + public Block(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain, int length, Block parentBlock) + throws ProtocolException { + super(params, payloadBytes, 0, parseLazy, parseRetain, length); + this.parentBlock = parentBlock; + } /** * Construct a block initialized with all the given fields. @@ -196,11 +215,20 @@ public class Block extends Message { headerBytesValid = parseRetain; } + private void parseAuxData() throws ProtocolException { + AuxPoWMessage auxPoWMessage = new AuxPoWMessage(payload, cursor); + auxPoWMessage.parse(); + this.cursor = auxPoWMessage.cursor; + this.parentBlock = new Block(params, auxPoWMessage.constructParentHeader()); + } + private void parseTransactions() throws ProtocolException { if (transactionsParsed) return; - cursor = offset + HEADER_SIZE; + if (version != BLOCK_VERSION_AUXPOW_AUXBLOCK) { + cursor = offset + HEADER_SIZE; + } optimalEncodingMessageSize = HEADER_SIZE; if (payload.length == cursor) { // This message is just a header, it has no transactions. @@ -229,6 +257,9 @@ public class Block extends Message { @Override void parse() throws ProtocolException { parseHeader(); + if (version == BLOCK_VERSION_AUXPOW_AUXBLOCK && payload.length >= 160) { // We have at least 2 headers in an Aux block. Workaround for StoredBlocks + parseAuxData(); + } parseTransactions(); length = cursor - offset; } @@ -516,6 +547,16 @@ public class Block extends Message { } } + private Sha256Hash calculateScryptHash() { + try { + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(HEADER_SIZE); + writeHeader(bos); + return new Sha256Hash(Utils.reverseBytes(scryptDigest(bos.toByteArray()))); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + /** * Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen on * the block explorer. If you call this on block 1 in the production chain @@ -525,6 +566,10 @@ public class Block extends Message { return getHash().toString(); } + public String getScryptHashAsString() { + return getScryptHash().toString(); + } + /** * Returns the hash of the block (which for a valid, solved block should be * below the target). Big endian. @@ -536,6 +581,12 @@ public class Block extends Message { return hash; } + public Sha256Hash getScryptHash() { + if (scryptHash == null) + scryptHash = calculateScryptHash(); + return scryptHash; + } + /** * The number that is one greater than the largest representable SHA-256 * hash. @@ -567,6 +618,9 @@ public class Block extends Message { block.difficultyTarget = difficultyTarget; block.transactions = null; block.hash = getHash().duplicate(); + if (version == BLOCK_VERSION_AUXPOW_AUXBLOCK) { + block.parentBlock = parentBlock; + } return block; } @@ -652,7 +706,12 @@ public class Block extends Message { // field is of the right value. This requires us to have the preceeding blocks. BigInteger target = getDifficultyTargetAsInteger(); - BigInteger h = getHash().toBigInteger(); + BigInteger h; + if (this.version == BLOCK_VERSION_AUXPOW_AUXBLOCK) { + h = this.parentBlock.getScryptHash().toBigInteger(); + } else { + h = getScryptHash().toBigInteger(); + } if (h.compareTo(target) > 0) { // Proof of work check failed! if (throwException) diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/FilteredBlock.java b/core/src/main/java/com/dogecoin/dogecoinj/core/FilteredBlock.java index db1058b6..366927a3 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/FilteredBlock.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/FilteredBlock.java @@ -26,7 +26,7 @@ import java.util.*; */ public class FilteredBlock extends Message { /** The protocol version at which Bloom filtering started to be supported. */ - public static final int MIN_PROTOCOL_VERSION = 70000; + public static final int MIN_PROTOCOL_VERSION = 70001; private Block header; private PartialMerkleTree merkleTree; @@ -57,12 +57,28 @@ public class FilteredBlock extends Message { @Override void parse() throws ProtocolException { + long blockVersion = Utils.readUint32(payload, cursor); + byte[] headerBytes = new byte[Block.HEADER_SIZE]; System.arraycopy(payload, 0, headerBytes, 0, Block.HEADER_SIZE); - header = new Block(params, headerBytes); - - merkleTree = new PartialMerkleTree(params, payload, Block.HEADER_SIZE); - + + + if (blockVersion == Block.BLOCK_VERSION_AUXPOW_AUXBLOCK) { + AuxPoWMessage auxPoWMessage = new AuxPoWMessage(payload, cursor + Block.HEADER_SIZE); + auxPoWMessage.parse(); + this.cursor = auxPoWMessage.cursor; + + header = new Block(params, headerBytes, new Block(params, auxPoWMessage.constructParentHeader())); + + byte[] filteredBlock = new byte[Block.HEADER_SIZE + payload.length - cursor]; + System.arraycopy(headerBytes, 0, filteredBlock, 0, headerBytes.length-1); + System.arraycopy(payload, cursor, filteredBlock, Block.HEADER_SIZE, payload.length-cursor); + merkleTree = new PartialMerkleTree(params, filteredBlock, Block.HEADER_SIZE); + } else { + header = new Block(params, headerBytes); + merkleTree = new PartialMerkleTree(params, payload, Block.HEADER_SIZE); + } + length = Block.HEADER_SIZE + merkleTree.getMessageSize(); } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/HeadersMessage.java b/core/src/main/java/com/dogecoin/dogecoinj/core/HeadersMessage.java index 3ae9cc06..a244a85e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/HeadersMessage.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/HeadersMessage.java @@ -76,17 +76,38 @@ public class HeadersMessage extends Message { long numHeaders = readVarInt(); if (numHeaders > MAX_HEADERS) throw new ProtocolException("Too many headers: got " + numHeaders + " which is larger than " + - MAX_HEADERS); + MAX_HEADERS); blockHeaders = new ArrayList(); for (int i = 0; i < numHeaders; ++i) { - // Read 80 bytes of the header and one more byte for the transaction list, which is always a 00 because the - // transaction list is empty. - byte[] blockHeader = readBytes(81); + // Read the block version. If it's not an aux block all is fine, else throw out the aux stuff + long blockVersion = Utils.readUint32(payload, cursor); + byte[] blockHeader; + Block newBlockHeader; + if (blockVersion == Block.BLOCK_VERSION_AUXPOW_AUXBLOCK) { + blockHeader = readBytes(80); + byte[] tmp = new byte[81]; + System.arraycopy(blockHeader, 0, tmp, 0, blockHeader.length); + tmp[80] = 0; + blockHeader = tmp; + + AuxPoWMessage auxPoWMessage = new AuxPoWMessage(payload, cursor); + auxPoWMessage.parse(); + this.cursor = auxPoWMessage.cursor; + + if (readBytes(1)[0] != 0) + throw new ProtocolException("Block header does not end with a null byte"); + + newBlockHeader = new Block(this.params, blockHeader, true, true, 81, new Block(params, auxPoWMessage.constructParentHeader())); + } else { + // Read 80 bytes of the header and one more byte for the transaction list, which is always a 00 because the + // transaction list is empty. + blockHeader = readBytes(81); + newBlockHeader = new Block(this.params, blockHeader, true, true, 81); + } if (blockHeader[80] != 0) throw new ProtocolException("Block header does not end with a null byte"); - Block newBlockHeader = new Block(this.params, blockHeader, true, true, 81); blockHeaders.add(newBlockHeader); } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java b/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java index 7653a86c..c5a89dcf 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java @@ -43,7 +43,7 @@ public abstract class NetworkParameters implements Serializable { /** * The protocol version this library implements. */ - public static final int PROTOCOL_VERSION = 70001; + public static final int PROTOCOL_VERSION = 70003; /** * The alert signing key originally owned by Satoshi, and now passed on to Gavin along with a few others. diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java index 00535b93..665109df 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java @@ -125,6 +125,8 @@ public class Peer extends PeerSocketHandler { private final HashSet pendingBlockDownloads = new HashSet(); // The lowest version number we're willing to accept. Lower than this will result in an immediate disconnect. private volatile int vMinProtocolVersion = Pong.MIN_PROTOCOL_VERSION; + // A string to be checked inside the subversion to distinguis true 70001 nodes from 1.4.2 nodes. + private String ACCEPTED_SUBVERSION = "Shibetoshi"; // When an API user explicitly requests a block or transaction from a peer, the InventoryItem is put here // whilst waiting for the response. Is not used for downloads Peer generates itself. private static class GetDataRequest { @@ -152,6 +154,10 @@ public class Peer extends PeerSocketHandler { // A future representing the results of doing a getUTXOs call. @Nullable private SettableFuture utxosFuture; + // A minimum needed block height to allow the implementation to connect to a peer. + // Is usually kept somewhen after a fork. + private final long MIN_PEER_BLOCK_HEIGHT = 380000; + /** *

Construct a peer that reads/writes from the given block chain.

* @@ -390,6 +396,18 @@ public class Peer extends PeerSocketHandler { vPeerVersionMessage.clientVersion, version); close(); } + if (vPeerVersionMessage.bestHeight < MIN_PEER_BLOCK_HEIGHT && params.getId().equals(NetworkParameters.ID_MAINNET)) + { + log.warn("Connected to a peer with just {} blocks. Don't accept it.", + vPeerVersionMessage.bestHeight); + close(); + } + if (!vPeerVersionMessage.subVer.contains(ACCEPTED_SUBVERSION) && params.getId().equals(NetworkParameters.ID_MAINNET)) + { + log.warn("Connected to a peer with subVer {}. Don't accept it.", + vPeerVersionMessage.subVer); + close(); + } } else if (m instanceof UTXOsMessage) { if (utxosFuture != null) { SettableFuture future = utxosFuture; diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java b/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java index 8ff03e36..59602fd8 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java @@ -17,6 +17,7 @@ package com.dogecoin.dogecoinj.core; +import com.dogecoin.dogecoinj.params.MainNetParams; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -113,7 +114,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac // until we reach this count. @GuardedBy("lock") private int maxConnections; // Minimum protocol version we will allow ourselves to connect to: require Bloom filtering. - private volatile int vMinRequiredProtocolVersion = FilteredBlock.MIN_PROTOCOL_VERSION; + private volatile int vMinRequiredProtocolVersion = MainNetParams.PROTOCOL_VERSION; // Runs a background thread that we use for scheduling pings to our peers, so we can measure their performance // and network latency. We ping peers every pingIntervalMsec milliseconds. diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Pong.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Pong.java index 43281973..d2a5051d 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Pong.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Pong.java @@ -21,7 +21,7 @@ import java.io.OutputStream; public class Pong extends Message { /** The smallest protocol version that supports the pong response (BIP 31). Anything beyond version 60000. */ - public static final int MIN_PROTOCOL_VERSION = 60001; + public static final int MIN_PROTOCOL_VERSION = 60003; private long nonce; diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Transaction.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Transaction.java index ec7205fe..a2f7745f 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Transaction.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Transaction.java @@ -92,14 +92,14 @@ public class Transaction extends ChildMessage implements Serializable { * If fee is lower than this value (in satoshis), a default reference client will treat it as if there were no fee. * Currently this is 10000 satoshis. */ - public static final Coin REFERENCE_DEFAULT_MIN_TX_FEE = Coin.valueOf(10000); + public static final Coin REFERENCE_DEFAULT_MIN_TX_FEE = Coin.COIN; // 1 DOGE min fee /** * Any standard (ie pay-to-address) output smaller than this value (in satoshis) will most likely be rejected by the network. * This is calculated by assuming a standard output will be 34 bytes, and then using the formula used in - * {@link TransactionOutput#getMinNonDustValue(Coin)}. Currently it's 5460 satoshis. + * {@link TransactionOutput#getMinNonDustValue(Coin)}. */ - public static final Coin MIN_NONDUST_OUTPUT = Coin.valueOf(5460); + public static final Coin MIN_NONDUST_OUTPUT = Coin.SATOSHI; //DOGE: We can send one "shibetoshi" but this will cost us extra fee! // These are serialized in both bitcoin and java serialization. private long version; diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionOutput.java b/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionOutput.java index 03f68a03..cb6a83bb 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionOutput.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionOutput.java @@ -246,9 +246,13 @@ public class TransactionOutput extends ChildMessage implements Serializable { // formula is wrong for anything that's not a pay-to-address output, unfortunately, we must follow the reference // clients wrongness in order to ensure we're considered standard. A better formula would either estimate the // size of data needed to satisfy all different script types, or just hard code 33 below. - final long size = this.bitcoinSerialize().length + 148; - Coin[] nonDustAndRemainder = feePerKbRequired.multiply(size).divideAndRemainder(1000); - return nonDustAndRemainder[1].equals(Coin.ZERO) ? nonDustAndRemainder[0] : nonDustAndRemainder[0].add(Coin.SATOSHI); + + // DOGE doesn't enforce these rules. Therefore we consider each output as valid. + return Coin.ZERO; + /**final BigInteger size = BigInteger.valueOf(this.bitcoinSerialize().length + 148); + BigInteger[] nonDustAndRemainder = feePerKbRequired.multiply(size).divideAndRemainder(BigInteger.valueOf(1000)); + return nonDustAndRemainder[1].equals(BigInteger.ZERO) ? nonDustAndRemainder[0] : nonDustAndRemainder[0].add(BigInteger.ONE);**/ + } /** diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Utils.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Utils.java index b5efe240..3fbe8026 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Utils.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Utils.java @@ -25,6 +25,7 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.Resources; import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedLongs; +import com.lambdaworks.crypto.SCrypt; import org.spongycastle.crypto.digests.RIPEMD160Digest; import java.io.ByteArrayOutputStream; @@ -159,6 +160,14 @@ public class Utils { } } + public static byte[] scryptDigest(byte[] input) { + try { + return SCrypt.scrypt(input, input, 1024, 1, 1, 32); + } catch (Exception e) { + return null; + } + } + public static byte[] singleDigest(byte[] input, int offset, int length) { synchronized (digest) { digest.reset(); diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/VersionMessage.java b/core/src/main/java/com/dogecoin/dogecoinj/core/VersionMessage.java index 694aad8d..267b6603 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/VersionMessage.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/VersionMessage.java @@ -77,7 +77,7 @@ public class VersionMessage extends Message { /** The version of this library release, as a string. */ public static final String BITCOINJ_VERSION = "0.13-SNAPSHOT"; /** The value that is prepended to the subVer field of this application. */ - public static final String LIBRARY_SUBVER = "/bitcoinj:" + BITCOINJ_VERSION + "/"; + public static final String LIBRARY_SUBVER = "/dogecoinj:" + BITCOINJ_VERSION + "/"; public VersionMessage(NetworkParameters params, byte[] payload) throws ProtocolException { super(params, payload, 0); diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java index 3347c7b1..9e014e16 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java @@ -3348,6 +3348,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha // If any inputs have already been added, we don't need to get their value from wallet Coin totalInput = Coin.ZERO; + Coin totalOutput = value; for (TransactionInput input : req.tx.getInputs()) if (input.getConnectedOutput() != null) totalInput = totalInput.add(input.getConnectedOutput().getValue()); @@ -3357,15 +3358,17 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha List originalInputs = new ArrayList(req.tx.getInputs()); - // We need to know if we need to add an additional fee because one of our values are smaller than 0.01 BTC + // We need to know if we need to add an additional fee because one of our values are smaller than 1 DOGE boolean needAtLeastReferenceFee = false; + int txOutDustFeeCount = 0; if (req.ensureMinRequiredFee && !req.emptyWallet) { // min fee checking is handled later for emptyWallet for (TransactionOutput output : req.tx.getOutputs()) - if (output.getValue().compareTo(Coin.CENT) < 0) { - if (output.getValue().compareTo(output.getMinNonDustValue()) < 0) - throw new DustySendRequested(); + if (output.getValue().compareTo(Coin.COIN) < 0) { //TXOut lower than 1 DOGE have a 2 DOGE fee! + //TODO Currently Dogecoin doesn't have this. We can put it back in later. +// if (output.getValue().compareTo(output.getMinNonDustValue()) < 0) +// throw new IllegalArgumentException("Tried to send dust with ensureMinRequiredFee set - no way to complete this"); needAtLeastReferenceFee = true; - break; + txOutDustFeeCount++; //DOGE: Each TXOut < 1 DOGE needs +1 DOGE fee! } } @@ -3381,7 +3384,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha if (!req.emptyWallet) { // This can throw InsufficientMoneyException. FeeCalculation feeCalculation; - feeCalculation = calculateFee(req, value, originalInputs, needAtLeastReferenceFee, candidates); + feeCalculation = calculateFee(req, value, originalInputs, needAtLeastReferenceFee, candidates, txOutDustFeeCount); bestCoinSelection = feeCalculation.bestCoinSelection; bestChangeOutput = feeCalculation.bestChangeOutput; } else { @@ -3393,6 +3396,10 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha candidates = null; // Selector took ownership and might have changed candidates. Don't access again. req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered); log.info(" emptying {}", bestCoinSelection.valueGathered.toFriendlyString()); + totalOutput = bestCoinSelection.valueGathered; + if (totalOutput.isLessThan(Coin.valueOf(2, 0))) + throw new InsufficientMoneyException(totalOutput.subtract(Coin.valueOf(2,0)), "Can't spend this due to fee."); + } for (TransactionOutput output : bestCoinSelection.gathered) @@ -3404,10 +3411,18 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha Transaction tx = req.tx; if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb)) throw new CouldNotAdjustDownwards(); + + //Set total output again after we reduced it by the fee. There are 3 cases here at emptying: + //1: Balance is >= 2: The fee will be 1 (or more if the size of the tx is bigger. + //2: Balance is >= 1 but < 2: The fee will be 1, but the output will be below 1 so additional fee is needed, which we don't have. + //3: Balance is > 0 but < 1: We can never spend this due to fee. + //4: Balance is 0: This won't happen here. + totalOutput = tx.getOutput(0).getValue(); } if (bestChangeOutput != null) { req.tx.addOutput(bestChangeOutput); + totalOutput = totalOutput.add(bestChangeOutput.getValue()); log.info(" with {} change", bestChangeOutput.getValue().toFriendlyString()); } @@ -3425,7 +3440,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha if (size > Transaction.MAX_STANDARD_TX_SIZE) throw new ExceededMaxTransactionSize(); - final Coin calculatedFee = req.tx.getFee(); + final Coin calculatedFee = totalInput.subtract(totalOutput); if (calculatedFee != null) { log.info(" with a fee of {}", calculatedFee.toFriendlyString()); } @@ -3987,7 +4002,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha //region Fee calculation code public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs, - boolean needAtLeastReferenceFee, LinkedList candidates) throws InsufficientMoneyException { + boolean needAtLeastReferenceFee, LinkedList candidates, + int txOutDustFeeCount ) throws InsufficientMoneyException { checkState(lock.isHeldByCurrentThread()); FeeCalculation result = new FeeCalculation(); // There are 3 possibilities for what adding change might do: @@ -4018,7 +4034,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha fees = fees.add(req.feePerKb); // First time around the loop. } if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) - fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; + fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; + //DOGE: Add 1 DOGE fee per txOut < 1 DOGE + if (txOutDustFeeCount > 0) + fees = fees.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(txOutDustFeeCount)); + valueNeeded = value.add(fees); if (additionalValueForNextCategory != null) @@ -4049,12 +4069,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha if (additionalValueSelected != null) change = change.add(additionalValueSelected); - // If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network - if (req.ensureMinRequiredFee && !change.equals(Coin.ZERO) && - change.compareTo(Coin.CENT) < 0 && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) { + // If change is < 1 DOGE, we will need to have at least minfee to be accepted by the network + if (req.ensureMinRequiredFee && !change.equals(Coin.ZERO) && change.isLessThan(Coin.COIN)) { // This solution may fit into category 2, but it may also be category 3, we'll check that later eitherCategory2Or3 = true; - additionalValueForNextCategory = Coin.CENT; + additionalValueForNextCategory = Coin.COIN; // If the change is smaller than the fee we want to add, this will be negative change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees)); } @@ -4070,11 +4089,13 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha changeAddress = getChangeAddress(); changeOutput = new TransactionOutput(params, req.tx, change, changeAddress); // If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee - if (req.ensureMinRequiredFee && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) { + if (req.ensureMinRequiredFee && Coin.COIN.compareTo(change) > 0) { // This solution definitely fits in category 3 isCategory3 = true; - additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add( - Transaction.MIN_NONDUST_OUTPUT.add(Coin.SATOSHI)); + //additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add( + // Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE)); + additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Coin.COIN); + //DOGE: We don't have a min value, but we add more fees for tx < 1 } else { size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size()) - VarInt.sizeOf(req.tx.getOutputs().size() - 1); // This solution is either category 1 or 2 @@ -4114,7 +4135,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha // If we are in selection2, we will require at least CENT additional. If we do that, there is no way // we can end up back here because CENT additional will always get us to 1 checkState(selection2 == null); - checkState(additionalValueForNextCategory.equals(Coin.CENT)); + checkState(additionalValueForNextCategory.equals(Coin.COIN)); selection2 = selection; selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3 } else { diff --git a/core/src/main/java/com/dogecoin/dogecoinj/utils/MonetaryFormat.java b/core/src/main/java/com/dogecoin/dogecoinj/utils/MonetaryFormat.java index 0b57330a..c6596300 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/utils/MonetaryFormat.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/utils/MonetaryFormat.java @@ -54,12 +54,12 @@ public final class MonetaryFormat { public static final MonetaryFormat UBTC = new MonetaryFormat().shift(6).minDecimals(0).optionalDecimals(2); /** Standard format for fiat amounts. */ public static final MonetaryFormat FIAT = new MonetaryFormat().shift(0).minDecimals(2).repeatOptionalDecimals(2, 1); - /** Currency code for base 1 Bitcoin. */ - public static final String CODE_BTC = "BTC"; - /** Currency code for base 1/1000 Bitcoin. */ - public static final String CODE_MBTC = "mBTC"; - /** Currency code for base 1/1000000 Bitcoin. */ - public static final String CODE_UBTC = "µBTC"; + /** Currency code for base 1 Dogecoin. */ + public static final String CODE_BTC = "DOGE"; + /** Currency code for base 1/1000 Dogecoin. */ + public static final String CODE_MBTC = "mDOGE"; + /** Currency code for base 1/1000000 Dogecoin. */ + public static final String CODE_UBTC = "µDOGE"; private final char negativeSign; private final char positiveSign;