3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 11:15:51 +00:00

Started connecting altcoinj to bitcoinj.

Merged in files from bitcoinj which were rejected for altcoin support.
This commit is contained in:
Ross Nicoll 2015-05-22 00:00:23 +01:00
parent c129d3fc89
commit 245505e984
78 changed files with 897 additions and 17546 deletions

View File

@ -0,0 +1,229 @@
/**
* Copyright 2011 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.bitcoinj.core;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import static org.bitcoinj.core.Coin.FIFTY_COINS;
import static org.bitcoinj.core.Utils.doubleDigest;
import static org.bitcoinj.core.Utils.doubleDigestTwoBuffers;
/**
* <p>A block is a group of transactions, and is one of the fundamental data structures of the Bitcoin system.
* It records a set of {@link Transaction}s together with some data that links it into a place in the global block
* chain, and proves that a difficult calculation was done over its contents. See
* <a href="http://www.bitcoin.org/bitcoin.pdf">the Bitcoin technical paper</a> for
* more detail on blocks. <p/>
*
* To get a block, you can either build one from the raw bytes you can get from another implementation, or request one
* 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_FLAG_AUXPOW = (1 << 8);
private boolean auxpowParsed = false;
private boolean auxpowBytesValid = false;
/** AuxPoW header element, if applicable. */
@Nullable private AuxPoW auxpow;
/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
AltcoinBlock(NetworkParameters params) {
super(params);
}
/** Constructs a block object from the Bitcoin wire format. */
public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) throws ProtocolException {
super(params, payloadBytes, 0, false, false, payloadBytes.length);
}
/**
* Contruct a block object from the Bitcoin wire format.
* @param params NetworkParameters object.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested.
* @param parseRetain Whether to retain the backing byte array for quick reserialization.
* If true and the backing byte array is invalidated due to modification of a field then
* the cached bytes may be repopulated and retained if the message is serialized again in the future.
* @param length The length of message if known. Usually this is provided when deserializing of the wire
* as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException
*/
public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain, int length)
throws ProtocolException {
super(params, payloadBytes, 0, parseLazy, parseRetain, length);
}
/**
* Contruct a block object from the Bitcoin wire format. Used in the case of a block
* contained within another message (i.e. for AuxPoW header).
*
* @param params NetworkParameters object.
* @param payloadBytes Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array.
* @param parent The message element which contains this block, maybe null for no parent.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested.
* @param parseRetain Whether to retain the backing byte array for quick reserialization.
* If true and the backing byte array is invalidated due to modification of a field then
* the cached bytes may be repopulated and retained if the message is serialized again in the future.
* @param length The length of message if known. Usually this is provided when deserializing of the wire
* as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException
*/
public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset, @Nullable Message parent, boolean parseLazy, boolean parseRetain, int length)
throws ProtocolException {
// TODO: Keep the parent
super(params, payloadBytes, offset, parseLazy, parseRetain, length);
}
/**
* Construct a block initialized with all the given fields.
* @param params Which network the block is for.
* @param version This should usually be set to 1 or 2, depending on if the height is in the coinbase input.
* @param prevBlockHash Reference to previous block in the chain or {@link Sha256Hash#ZERO_HASH} if genesis.
* @param merkleRoot The root of the merkle tree formed by the transactions.
* @param time UNIX time when the block was mined.
* @param difficultyTarget Number which this block hashes lower than.
* @param nonce Arbitrary number to make the block hash lower than the target.
* @param transactions List of transactions including the coinbase.
*/
public AltcoinBlock(NetworkParameters params, long version, Sha256Hash prevBlockHash, Sha256Hash merkleRoot, long time,
long difficultyTarget, long nonce, List<Transaction> transactions) {
super(params, version, prevBlockHash, merkleRoot, time, difficultyTarget, nonce, transactions);
}
@Override
protected void parseAuxPoW() throws ProtocolException {
if (this.auxpowParsed)
return;
if (this.params.isAuxPoWBlockVersion(this.version)) {
// 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, parseLazy, parseRetain);
} else {
this.auxpow = null;
}
this.auxpowParsed = true;
this.auxpowBytesValid = parseRetain;
}
@Override
void parse() throws ProtocolException {
parseHeader();
parseAuxPoW();
parseTransactions();
length = cursor - offset;
}
@Override
protected void parseLite() throws ProtocolException {
// Ignore the header since it has fixed length. If length is not provided we will have to
// invoke a light parse of transactions to calculate the length.
if (length == UNKNOWN_LENGTH) {
Preconditions.checkState(parseLazy,
"Performing lite parse of block transaction as block was initialised from byte array " +
"without providing length. This should never need to happen.");
parseTransactions();
// TODO: Handle AuxPoW header space
length = cursor - offset;
} else {
transactionBytesValid = !transactionsParsed || parseRetain && length > HEADER_SIZE;
}
headerBytesValid = !headerParsed || parseRetain && length >= HEADER_SIZE;
}
@Override
void writeHeader(OutputStream stream) throws IOException {
super.writeHeader(stream);
// TODO: Write the AuxPoW header
}
/** Returns a copy of the block, but without any transactions. */
@Override
public Block cloneAsHeader() {
maybeParseHeader();
AltcoinBlock block = new AltcoinBlock(params);
block.nonce = nonce;
block.prevBlockHash = prevBlockHash;
block.merkleRoot = getMerkleRoot();
block.version = version;
block.time = time;
block.difficultyTarget = difficultyTarget;
block.transactions = null;
block.hash = getHash();
block.auxpow = auxpow;
return block;
}
/** Returns true if the hash of the block is OK (lower than difficulty target). */
protected boolean checkProofOfWork(boolean throwException) throws VerificationException {
// TODO: Add AuxPoW support
// This part is key - it is what proves the block was as difficult to make as it claims
// to be. Note however that in the context of this function, the block can claim to be
// as difficult as it wants to be .... if somebody was able to take control of our network
// connection and fork us onto a different chain, they could send us valid blocks with
// ridiculously easy difficulty and this function would accept them.
//
// To prevent this attack from being possible, elsewhere we check that the difficultyTarget
// field is of the right value. This requires us to have the preceeding blocks.
BigInteger target = getDifficultyTargetAsInteger();
BigInteger h = getHash().toBigInteger();
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs "
+ target.toString(16));
else
return false;
}
return true;
}
/**
* Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically,
* throws an exception if the proof of work is invalid, or if the timestamp is too far from what it should be.
* This is <b>not</b> everything that is required for a block to be valid, only what is checkable independent
* of the chain and without a transaction index.
*
* @throws VerificationException
*/
@Override
public void verifyHeader() throws VerificationException {
super.verifyHeader();
}
}

View File

@ -0,0 +1,274 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
* Copyright 2015 J. 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.bitcoinj.core;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.wallet.WalletTransaction.Pool;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.*;
import java.util.*;
import static org.bitcoinj.core.Utils.*;
import static com.google.common.base.Preconditions.checkState;
/**
* <p>An AuxPoW header wraps a block header from another coin, enabling the foreign
* chain's proof of work to be used for this chain as well.</p>
*/
public class AuxPoW extends ChildMessage implements Serializable {
private static final Logger log = LoggerFactory.getLogger(AuxPoW.class);
private static final long serialVersionUID = -8567546957352643140L;
private Transaction transaction;
private Sha256Hash hashBlock;
private MerkleBranch coinbaseBranch;
private MerkleBranch blockchainBranch;
private Block parentBlockHeader;
// Transactions can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs) so that Blocks
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
public AuxPoW(NetworkParameters params, @Nullable Message parent) {
super(params);
transaction = new Transaction(params);
hashBlock = Sha256Hash.ZERO_HASH;
coinbaseBranch = new MerkleBranch(params, this);
blockchainBranch = new MerkleBranch(params, this);
parentBlockHeader = null;
}
/**
* Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed.
* @param params NetworkParameters object.1
* @param payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array.
* @param parent The message element which contains this header.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested.
* @param parseRetain Whether to retain the backing byte array for quick reserialization.
* If true and the backing byte array is invalidated due to modification of a field then
* the cached bytes may be repopulated and retained if the message is serialized again in the future.
* @throws ProtocolException
*/
public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, payload, offset, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH);
}
/**
* Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed.
*/
public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, payload, 0, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH);
}
@Override
protected void parseLite() throws ProtocolException {
length = calcLength(payload, offset);
cursor = offset + length;
}
protected static int calcLength(byte[] buf, int offset) {
VarInt varint;
// jump past transaction
int cursor = offset + Transaction.calcLength(buf, offset);
// jump past header hash
cursor += 4;
// Coin base branch
cursor += MerkleBranch.calcLength(buf, offset);
// Block chain branch
cursor += MerkleBranch.calcLength(buf, offset);
// Block header
cursor += Block.HEADER_SIZE;
return cursor - offset + 4;
}
@Override
void parse() throws ProtocolException {
if (parsed)
return;
cursor = offset;
transaction = new Transaction(params, payload, cursor, this, parseLazy, parseRetain, Message.UNKNOWN_LENGTH);
cursor += transaction.getOptimalEncodingMessageSize();
optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize();
hashBlock = readHash();
optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding
coinbaseBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain);
cursor += coinbaseBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize();
blockchainBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain);
cursor += blockchainBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += blockchainBranch.getOptimalEncodingMessageSize();
// Make a copy of JUST the contained block header, so the block parser doesn't try reading
// transactions past the end
byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE);
cursor += Block.HEADER_SIZE;
parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, parseLazy, parseRetain, Block.HEADER_SIZE);
length = cursor - offset;
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
maybeParse();
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
@Override
public String toString() {
return toString(null);
}
/**
* A human readable version of the transaction useful for debugging. The format is not guaranteed to be stable.
* @param chain If provided, will be used to estimate lock times (if set). Can be null.
*/
public String toString(@Nullable AbstractBlockChain chain) {
return transaction.toString(chain);
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
transaction.bitcoinSerialize(stream);
stream.write(Utils.reverseBytes(hashBlock.getBytes()));
coinbaseBranch.bitcoinSerialize(stream);
blockchainBranch.bitcoinSerialize(stream);
parentBlockHeader.bitcoinSerializeToStream(stream);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuxPoW input = (AuxPoW) o;
if (!transaction.equals(input.transaction)) return false;
if (!hashBlock.equals(input.hashBlock)) return false;
if (!coinbaseBranch.equals(input.hashBlock)) return false;
if (!blockchainBranch.equals(input.hashBlock)) return false;
if (!parentBlockHeader.equals(input.hashBlock)) return false;
return getHash().equals(input.getHash());
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + transaction.hashCode();
result = 31 * result + hashBlock.hashCode();
result = 31 * result + coinbaseBranch.hashCode();
result = 31 * result + blockchainBranch.hashCode();
result = 31 * result + parentBlockHeader.hashCode();
return result;
}
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
maybeParse();
out.defaultWriteObject();
}
/**
* Get the block header from the parent blockchain. The hash of the header
* is the value which should match the difficulty target. Note that blocks are
* not necessarily part of the parent blockchain, they simply must be valid
* blocks at the difficulty of the child blockchain.
*/
public Block getParentBlockHeader() {
return parentBlockHeader;
}
/**
* Get the coinbase transaction from the AuxPoW header. This should contain a
* reference back to the block hash in its input scripts, to prove that the
* transaction was created after the block.
*/
public Transaction getCoinbase() {
return transaction;
}
/**
* Get the Merkle branch used to connect the AuXPow header with this blockchain.
*/
public MerkleBranch getBlockchainBranch() {
return blockchainBranch;
}
/**
* Get the Merkle branch used to connect the coinbase transaction with this blockchain.
*/
public MerkleBranch getCoinbaseBranch() {
return coinbaseBranch;
}
/**
* <p>Checks the transaction contents for sanity, in ways that can be done in a standalone manner.
* Does <b>not</b> perform all checks on a transaction such as whether the inputs are already spent.
* Specifically this method verifies:</p>
*
* <ul>
* <li>That there is at least one input and output.</li>
* <li>That the serialized size is not larger than the max block size.</li>
* <li>That no outputs have negative value.</li>
* <li>That the outputs do not sum to larger than the max allowed quantity of coin in the system.</li>
* <li>If the tx is a coinbase tx, the coinbase scriptSig size is within range. Otherwise that there are no
* coinbase inputs in the tx.</li>
* </ul>
*
* @throws VerificationException
*/
public void verify() throws VerificationException {
maybeParse();
// TODO: Verify the AuxPoW data
}
}

View File

@ -0,0 +1,223 @@
/**
* Copyright 2011 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.bitcoinj.core;
import static org.bitcoinj.core.Utils.doubleDigestTwoBuffers;
import static org.bitcoinj.core.Utils.reverseBytes;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A Merkle branch contains the hashes from a leaf of a Merkle tree
* up to its root, plus a bitset used to define how the hashes are applied.
* Given the hash of the leaf, this can be used to calculate the tree
* root. This is useful for proving that a leaf belongs to a given tree.
*/
public class MerkleBranch extends ChildMessage implements Serializable {
private static final long serialVersionUID = 2;
// Merkle branches can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs) so that Blocks
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
private List<Sha256Hash> branchHashes;
private long branchSideMask;
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
super(params);
setParent(parent);
this.branchHashes = new ArrayList<Sha256Hash>();
this.branchSideMask = 0;
}
/**
* Deserializes an input message. This is usually part of a merkle branch message.
*/
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent, byte[] payload, int offset) throws ProtocolException {
super(params, payload, offset);
setParent(parent);
}
/**
* Deserializes an input message. This is usually part of a merkle branch message.
* @param params NetworkParameters object.
* @param payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested.
* @param parseRetain Whether to retain the backing byte array for quick reserialization.
* If true and the backing byte array is invalidated due to modification of a field then
* the cached bytes may be repopulated and retained if the message is serialized again in the future.
* as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException
*/
public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset,
boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, payload, offset, parent, parseLazy, parseRetain, UNKNOWN_LENGTH);
}
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent,
final List<Sha256Hash> hashes, final long branchSideMask) {
super(params);
setParent(parent);
this.branchHashes = hashes;
this.branchSideMask = branchSideMask;
}
@Override
protected void parseLite() throws ProtocolException {
length = calcLength(payload, offset);
cursor = offset + length;
}
protected static int calcLength(byte[] buf, int offset) {
VarInt varint = new VarInt(buf, offset);
return ((int) varint.value) * 4 + 4;
}
@Override
void parse() throws ProtocolException {
if (parsed)
return;
cursor = offset;
final int hashCount = (int) readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(hashCount);
branchHashes = new ArrayList<Sha256Hash>(hashCount);
for (int hashIdx = 0; hashIdx < hashCount; hashIdx++) {
branchHashes.add(readHash());
}
optimalEncodingMessageSize += 32 * hashCount;
branchSideMask = readUint32();
optimalEncodingMessageSize += 4;
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(new VarInt(branchHashes.size()).encode());
for (Sha256Hash hash: branchHashes) {
stream.write(Utils.reverseBytes(hash.getBytes()));
}
Utils.uint32ToByteStreamLE(branchSideMask, stream);
}
/**
* Calculate the merkle branch root based on the supplied hashes and the given leaf hash.
* Used to verify that the given leaf and root are part of the same tree.
*/
public Sha256Hash calculateMerkleRoot(final Sha256Hash leaf) {
byte[] target = reverseBytes(leaf.getBytes());
long mask = branchSideMask;
for (Sha256Hash hash: branchHashes) {
target = (mask & 1) == 0
? doubleDigestTwoBuffers(target, 0, 32, reverseBytes(hash.getBytes()), 0, 32)
: doubleDigestTwoBuffers(reverseBytes(hash.getBytes()), 0, 32, target, 0, 32);
mask >>= 1;
}
return new Sha256Hash(reverseBytes(target));
}
/**
* Get the hashes which make up this branch.
*/
public List<Sha256Hash> getHashes() {
return Collections.unmodifiableList(this.branchHashes);
}
/**
* Get the number of hashes in this branch.
*/
public int getSize() {
return branchHashes.size();
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
maybeParse();
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
/**
* Returns a human readable debug string.
*/
@Override
public String toString() {
return "Merkle branch";
}
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
maybeParse();
out.defaultWriteObject();
}
/**
* Should check that the merkle branch side bits are not wider than the
* provided hashes.
* @throws VerificationException If the branch is invalid.
*/
public void verify() throws VerificationException {
maybeParse();
// TODO: Check the flags make sense for the inputs
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MerkleBranch input = (MerkleBranch) o;
if (!branchHashes.equals(input.branchHashes)) return false;
if (branchSideMask != input.branchSideMask) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + branchHashes.hashCode();
result = 31 * result + (int) branchSideMask;
return result;
}
}

View File

@ -16,9 +16,14 @@
package org.altcoinj.params;
import org.altcoinj.core.NetworkParameters;
import org.altcoinj.core.Sha256Hash;
import org.altcoinj.core.Utils;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.MonetaryFormat;
import static com.google.common.base.Preconditions.checkState;
@ -26,27 +31,55 @@ import static com.google.common.base.Preconditions.checkState;
* Parameters for the main Dogecoin production network on which people trade goods and services.
*/
public class AbstractDogecoinParams extends NetworkParameters {
/** Standard format for the DOGE denomination. */
public static final MonetaryFormat DOGE;
/** Standard format for the mDOGE denomination. */
public static final MonetaryFormat MDOGE;
/** Standard format for the Koinu denomination. */
public static final MonetaryFormat KOINU;
public static final int DOGE_TARGET_TIMESPAN = 4 * 60 * 60; // 4 hours per difficulty cycle, on average.
public static final int DOGE_TARGET_TIMESPAN_NEW = 60; // 60s per difficulty cycle, on average. Kicks in after block 145k.
public static final int DOGE_TARGET_SPACING = 1 * 60; // 1 minute per block.
public static final int DOGE_INTERVAL = TARGET_TIMESPAN / TARGET_SPACING;
public static final int DOGE_INTERVAL_NEW = TARGET_TIMESPAN_NEW / TARGET_SPACING;
public static final int DOGE_INTERVAL = DOGE_TARGET_TIMESPAN / DOGE_TARGET_SPACING;
public static final int DOGE_INTERVAL_NEW = DOGE_TARGET_TIMESPAN_NEW / DOGE_TARGET_SPACING;
/** Currency code for base 1 Dogecoin. */
public static final String CODE_DOGE = "DOGE";
/** Currency code for base 1/1,000 Dogecoin. */
public static final String CODE_MDOGE = "mDOGE";
/** Currency code for base 1/100,000,000 Dogecoin. */
public static final String CODE_KOINU = "Koinu";
private static final Map<Integer, String> CURRENCY_CODES = new HashMap<Integer, String>();
static {
CURRENCY_CODES.put(0, CODE_DOGE);
CURRENCY_CODES.put(3, CODE_MDOGE);
CURRENCY_CODES.put(8, CODE_KOINU);
DOGE = MonetaryFormat.BTC.replaceCodes(CURRENCY_CODES);
MDOGE = DOGE.shift(3).minDecimals(2).optionalDecimals(2);
KOINU = DOGE.shift(6).minDecimals(0).optionalDecimals(2);
}
/** The string returned by getId() for the main, production network where people trade things. */
public static final String ID_DOGE_MAINNET = "org.dogecoin.production";
/** The string returned by getId() for the testnet. */
public static final String ID_DOGE_TESTNET = "org.dogecoin.test";
protected int newInterval;
protected int newTargetTimespan;
protected final int newInterval;
protected final int newTargetTimespan;
protected final int diffChangeTarget;
public AbstractDogecoinParams() {
public AbstractDogecoinParams(final int setDiffChangeTarget) {
super();
interval = DOGE_INTERVAL;
newInterval = DOGE_INTERVAL_NEW;
targetTimespan = DOGE_TARGET_TIMESPAN;
newTargetTimespan = DOGE_TARGET_TIMESPAN_NEW;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
diffChangeTarget = setDiffChangeTarget;
packetMagic = 0xc0c0c0c0;
bip32HeaderPub = 0x0488C42E; //The 4 byte header that serializes in base58 to "xpub". (?)
@ -67,5 +100,7 @@ public class AbstractDogecoinParams extends NetworkParameters {
return newTargetTimespan;
}
public MonetaryFormat
public MonetaryFormat getMonetaryFormat() {
return DOGE;
}
}

View File

@ -16,9 +16,12 @@
package org.altcoinj.params;
import org.altcoinj.core.NetworkParameters;
import org.altcoinj.core.Sha256Hash;
import org.altcoinj.core.Utils;
import java.util.Map;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.MonetaryFormat;
import static com.google.common.base.Preconditions.checkState;
@ -26,8 +29,10 @@ import static com.google.common.base.Preconditions.checkState;
* Parameters for the main production network on which people trade goods and services.
*/
public class DogecoinMainNetParams extends AbstractDogecoinParams {
protected static final int DIFFICULTY_CHANGE_TARGET = 145000;
public DogecoinMainNetParams() {
super();
super(DIFFICULTY_CHANGE_TARGET);
dumpedPrivateKeyHeader = 158; //This is always addressHeader + 128
addressHeader = 30;
p2shHeader = 22;
@ -43,8 +48,6 @@ public class DogecoinMainNetParams extends AbstractDogecoinParams {
subsidyDecreaseBlockCount = 100000;
spendableCoinbaseDepth = 100;
diffChangeTarget = 145000;
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691"),
genesisHash);

View File

@ -17,8 +17,8 @@
package org.altcoinj.params;
import org.altcoinj.core.NetworkParameters;
import org.altcoinj.core.Utils;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.spongycastle.util.encoders.Hex;
import static com.google.common.base.Preconditions.checkState;
@ -28,8 +28,10 @@ import static com.google.common.base.Preconditions.checkState;
* and testing of applications and new Bitcoin versions.
*/
public class DogecoinTestNet3Params extends AbstractDogecoinParams {
protected static final int DIFFICULTY_CHANGE_TARGET = 145000;
public DogecoinTestNet3Params() {
super();
super(DIFFICULTY_CHANGE_TARGET);
id = ID_DOGE_TESTNET;
// Genesis hash is bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e
packetMagic = 0xfcc1b7dc;
@ -49,8 +51,6 @@ public class DogecoinTestNet3Params extends AbstractDogecoinParams {
checkState(genesisHash.equals("bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e"));
alertSigningKey = Hex.decode("042756726da3c7ef515d89212ee1705023d14be389e25fe15611585661b9a20021908b2b80a3c7200a0139dd2b26946606aab0eef9aa7689a6dc2c7eee237fa834");
diffChangeTarget = 145000;
dnsSeeds = new String[] {
"testnets.chain.so" // Chain.so
};
@ -61,7 +61,7 @@ public class DogecoinTestNet3Params extends AbstractDogecoinParams {
private static DogecoinTestNet3Params instance;
public static synchronized DogecoinTestNet3Params get() {
if (instance == null) {
instance = new TestNet3Params();
instance = new DogecoinTestNet3Params();
}
return instance;
}

View File

@ -1,423 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.protocols.payments;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.X509Utils;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.bitcoin.protocols.payments.Protos;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Utility methods and constants for working with <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">
* BIP 70 aka the payment protocol</a>. These are low level wrappers around the protocol buffers. If you're implementing
* a wallet app, look at {@link PaymentSession} for a higher level API that should simplify working with the protocol.</p>
*
* <p>BIP 70 defines a binary, protobuf based protocol that runs directly between sender and receiver of funds. Payment
* protocol data does not flow over the Bitcoin P2P network or enter the block chain. It's instead for data that is only
* of interest to the parties involved but isn't otherwise needed for consensus.</p>
*/
public class PaymentProtocol {
// MIME types as defined in DIP71.
public static final String MIMETYPE_PAYMENTREQUEST = "application/vnd.doge.payment.request";
public static final String MIMETYPE_PAYMENT = "application/vnd.doge.payment.payment";
public static final String MIMETYPE_PAYMENTACK = "application/vnd.doge.payment.ack";
/**
* Create a payment request with one standard pay to address output. You may want to sign the request using
* {@link #signPaymentRequest}. Use {@link Protos.PaymentRequest.Builder#build} to get the actual payment
* request.
*
* @param params network parameters
* @param amount amount of coins to request, or null
* @param toAddress address to request coins to
* @param memo arbitrary, user readable memo, or null if none
* @param paymentUrl URL to send payment message to, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment request, in its builder form
*/
public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params,
@Nullable Coin amount, Address toAddress, @Nullable String memo, @Nullable String paymentUrl,
@Nullable byte[] merchantData) {
return createPaymentRequest(params, ImmutableList.of(createPayToAddressOutput(amount, toAddress)), memo,
paymentUrl, merchantData);
}
/**
* Create a payment request. You may want to sign the request using {@link #signPaymentRequest}. Use
* {@link Protos.PaymentRequest.Builder#build} to get the actual payment request.
*
* @param params network parameters
* @param outputs list of outputs to request coins to
* @param memo arbitrary, user readable memo, or null if none
* @param paymentUrl URL to send payment message to, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment request, in its builder form
*/
public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params,
List<Protos.Output> outputs, @Nullable String memo, @Nullable String paymentUrl,
@Nullable byte[] merchantData) {
final Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
paymentDetails.setNetwork(params.getPaymentProtocolId());
for (Protos.Output output : outputs)
paymentDetails.addOutputs(output);
if (memo != null)
paymentDetails.setMemo(memo);
if (paymentUrl != null)
paymentDetails.setPaymentUrl(paymentUrl);
if (merchantData != null)
paymentDetails.setMerchantData(ByteString.copyFrom(merchantData));
paymentDetails.setTime(Utils.currentTimeSeconds());
final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
return paymentRequest;
}
/**
* Parse a payment request.
*
* @param paymentRequest payment request to parse
* @return instance of {@link PaymentSession}, used as a value object
* @throws PaymentProtocolException
*/
public static PaymentSession parsePaymentRequest(Protos.PaymentRequest paymentRequest)
throws PaymentProtocolException {
return new PaymentSession(paymentRequest, false, null);
}
/**
* Sign the provided payment request.
*
* @param paymentRequest Payment request to sign, in its builder form.
* @param certificateChain Certificate chain to send with the payment request, ordered from client certificate to root
* certificate. The root certificate itself may be omitted.
* @param privateKey The key to sign with. Must match the public key from the first certificate of the certificate chain.
*/
public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentRequest,
X509Certificate[] certificateChain, PrivateKey privateKey) {
try {
final Protos.X509Certificates.Builder certificates = Protos.X509Certificates.newBuilder();
for (final Certificate certificate : certificateChain)
certificates.addCertificate(ByteString.copyFrom(certificate.getEncoded()));
paymentRequest.setPkiType("x509+sha256");
paymentRequest.setPkiData(certificates.build().toByteString());
paymentRequest.setSignature(ByteString.EMPTY);
final Protos.PaymentRequest paymentRequestToSign = paymentRequest.build();
final String algorithm;
if (privateKey.getAlgorithm().equalsIgnoreCase("RSA"))
algorithm = "SHA256withRSA";
else
throw new IllegalStateException(privateKey.getAlgorithm());
final Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(paymentRequestToSign.toByteArray());
paymentRequest.setSignature(ByteString.copyFrom(signature.sign()));
} catch (final GeneralSecurityException x) {
// Should never happen so don't make users have to think about it.
throw new RuntimeException(x);
}
}
/**
* Uses the provided PKI method to find the corresponding public key and verify the provided signature.
*
* @param paymentRequest Payment request to verify.
* @param trustStore KeyStore of trusted root certificate authorities.
* @return verification data, or null if no PKI method was specified in the {@link Protos.PaymentRequest}.
* @throws PaymentProtocolException if payment request could not be verified.
*/
public static @Nullable PkiVerificationData verifyPaymentRequestPki(Protos.PaymentRequest paymentRequest, KeyStore trustStore)
throws PaymentProtocolException {
List<X509Certificate> certs = null;
try {
final String pkiType = paymentRequest.getPkiType();
if (pkiType.equals("none"))
// Nothing to verify. Everything is fine. Move along.
return null;
String algorithm;
if (pkiType.equals("x509+sha256"))
algorithm = "SHA256withRSA";
else if (pkiType.equals("x509+sha1"))
algorithm = "SHA1withRSA";
else
throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType);
Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData());
if (protoCerts.getCertificateCount() == 0)
throw new PaymentProtocolException.InvalidPkiData("No certificates provided in message: server config error");
// Parse the certs and turn into a certificate chain object. Cert factories can parse both DER and base64.
// The ordering of certificates is defined by the payment protocol spec to be the same as what the Java
// crypto API requires - convenient!
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
certs = Lists.newArrayList();
for (ByteString bytes : protoCerts.getCertificateList())
certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput()));
CertPath path = certificateFactory.generateCertPath(certs);
// Retrieves the most-trusted CAs from keystore.
PKIXParameters params = new PKIXParameters(trustStore);
// Revocation not supported in the current version.
params.setRevocationEnabled(false);
// Now verify the certificate chain is correct and trusted. This let's us get an identity linked pubkey.
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params);
PublicKey publicKey = result.getPublicKey();
// OK, we got an identity, now check it was used to sign this message.
Signature signature = Signature.getInstance(algorithm);
// Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most obvious
// way to set it up, because we don't care about the constraints specified on the certificates: any
// cert that links a key to a domain name or other identity will do for us.
signature.initVerify(publicKey);
Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder();
reqToCheck.setSignature(ByteString.EMPTY);
signature.update(reqToCheck.build().toByteArray());
if (!signature.verify(paymentRequest.getSignature().toByteArray()))
throw new PaymentProtocolException.PkiVerificationException("Invalid signature, this payment request is not valid.");
// Signature verifies, get the names from the identity we just verified for presentation to the user.
final X509Certificate cert = certs.get(0);
String displayName = X509Utils.getDisplayNameFromCertificate(cert, true);
if (displayName == null)
throw new PaymentProtocolException.PkiVerificationException("Could not extract name from certificate");
// Everything is peachy. Return some useful data to the caller.
return new PkiVerificationData(displayName, publicKey, result.getTrustAnchor());
} catch (InvalidProtocolBufferException e) {
// Data structures are malformed.
throw new PaymentProtocolException.InvalidPkiData(e);
} catch (CertificateException e) {
// The X.509 certificate data didn't parse correctly.
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (NoSuchAlgorithmException e) {
// Should never happen so don't make users have to think about it. PKIX is always present.
throw new RuntimeException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch (CertPathValidatorException e) {
// The certificate chain isn't known or trusted, probably, the server is using an SSL root we don't
// know about and the user needs to upgrade to a new version of the software (or import a root cert).
throw new PaymentProtocolException.PkiVerificationException(e, certs);
} catch (InvalidKeyException e) {
// Shouldn't happen if the certs verified correctly.
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (SignatureException e) {
// Something went wrong during hashing (yes, despite the name, this does not mean the sig was invalid).
throw new PaymentProtocolException.PkiVerificationException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
}
/**
* Information about the X.509 signature's issuer and subject.
*/
public static class PkiVerificationData {
/** Display name of the payment requestor, could be a domain name, email address, legal name, etc */
public final String displayName;
/** SSL public key that was used to sign. */
public final PublicKey merchantSigningKey;
/** Object representing the CA that verified the merchant's ID */
public final TrustAnchor rootAuthority;
/** String representing the display name of the CA that verified the merchant's ID */
public final String rootAuthorityName;
private PkiVerificationData(@Nullable String displayName, PublicKey merchantSigningKey,
TrustAnchor rootAuthority) throws PaymentProtocolException.PkiVerificationException {
try {
this.displayName = displayName;
this.merchantSigningKey = merchantSigningKey;
this.rootAuthority = rootAuthority;
this.rootAuthorityName = X509Utils.getDisplayNameFromCertificate(rootAuthority.getTrustedCert(), true);
} catch (CertificateParsingException x) {
throw new PaymentProtocolException.PkiVerificationException(x);
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("displayName", displayName)
.add("rootAuthorityName", rootAuthorityName)
.add("merchantSigningKey", merchantSigningKey)
.add("rootAuthority", rootAuthority)
.toString();
}
}
/**
* Create a payment message with one standard pay to address output.
*
* @param transactions one or more transactions that satisfy the requested outputs.
* @param refundAmount amount of coins to request as a refund, or null if no refund.
* @param refundAddress address to refund coins to
* @param memo arbitrary, user readable memo, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment message
*/
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
@Nullable Coin refundAmount, @Nullable Address refundAddress, @Nullable String memo,
@Nullable byte[] merchantData) {
if (refundAddress != null) {
if (refundAmount == null)
throw new IllegalArgumentException("Specify refund amount if refund address is specified.");
return createPaymentMessage(transactions,
ImmutableList.of(createPayToAddressOutput(refundAmount, refundAddress)), memo, merchantData);
} else {
return createPaymentMessage(transactions, null, memo, merchantData);
}
}
/**
* Create a payment message. This wraps up transaction data along with anything else useful for making a payment.
*
* @param transactions transactions to include with the payment message
* @param refundOutputs list of outputs to refund coins to, or null
* @param memo arbitrary, user readable memo, or null if none
* @param merchantData arbitrary merchant data, or null if none
* @return created payment message
*/
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
@Nullable List<Protos.Output> refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) {
Protos.Payment.Builder builder = Protos.Payment.newBuilder();
for (Transaction transaction : transactions) {
transaction.verify();
builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize()));
}
if (refundOutputs != null) {
for (Protos.Output output : refundOutputs)
builder.addRefundTo(output);
}
if (memo != null)
builder.setMemo(memo);
if (merchantData != null)
builder.setMerchantData(ByteString.copyFrom(merchantData));
return builder.build();
}
/**
* Parse transactions from payment message.
*
* @param params network parameters (needed for transaction deserialization)
* @param paymentMessage payment message to parse
* @return list of transactions
*/
public static List<Transaction> parseTransactionsFromPaymentMessage(NetworkParameters params,
Protos.Payment paymentMessage) {
final List<Transaction> transactions = new ArrayList<Transaction>(paymentMessage.getTransactionsCount());
for (final ByteString transaction : paymentMessage.getTransactionsList())
transactions.add(new Transaction(params, transaction.toByteArray()));
return transactions;
}
/**
* Message returned by the merchant in response to a Payment message.
*/
public static class Ack {
@Nullable private final String memo;
Ack(@Nullable String memo) {
this.memo = memo;
}
/**
* Returns the memo included by the merchant in the payment ack. This message is typically displayed to the user
* as a notification (e.g. "Your payment was received and is being processed"). If none was provided, returns
* null.
*/
@Nullable public String getMemo() {
return memo;
}
}
/**
* Create a payment ack.
*
* @param paymentMessage payment message to send with the ack
* @param memo arbitrary, user readable memo, or null if none
* @return created payment ack
*/
public static Protos.PaymentACK createPaymentAck(Protos.Payment paymentMessage, @Nullable String memo) {
final Protos.PaymentACK.Builder builder = Protos.PaymentACK.newBuilder();
builder.setPayment(paymentMessage);
if (memo != null)
builder.setMemo(memo);
return builder.build();
}
/**
* Parse payment ack into an object.
*/
public static Ack parsePaymentAck(Protos.PaymentACK paymentAck) {
final String memo = paymentAck.hasMemo() ? paymentAck.getMemo() : null;
return new Ack(memo);
}
/**
* Create a standard pay to address output for usage in {@link #createPaymentRequest} and
* {@link #createPaymentMessage}.
*
* @param amount amount to pay, or null
* @param address address to pay to
* @return output
*/
public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Address address) {
Protos.Output.Builder output = Protos.Output.newBuilder();
if (amount != null) {
if (amount.compareTo(NetworkParameters.MAX_MONEY) > 0)
throw new IllegalArgumentException("Amount too big: " + amount);
output.setAmount(amount.value);
} else {
output.setAmount(0);
}
output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).getProgram()));
return output.build();
}
/**
* Value object to hold amount/script pairs.
*/
public static class Output implements Serializable {
public final @Nullable Coin amount;
public final byte[] scriptData;
public Output(@Nullable Coin amount, byte[] scriptData) {
this.amount = amount;
this.scriptData = scriptData;
}
}
}

View File

@ -1,5 +0,0 @@
/**
* The BIP70 payment protocol wraps Bitcoin transactions and adds various useful features like memos, refund addresses
* and authentication.
*/
package com.dogecoin.dogecoinj.protocols.payments;

View File

@ -1,451 +0,0 @@
/*
* 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.altcoinj.utils;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.math.LongMath.checkedMultiply;
import static com.google.common.math.LongMath.checkedPow;
import static com.google.common.math.LongMath.divide;
import java.math.RoundingMode;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.altcoinj.core.Coin;
import org.altcoinj.core.Monetary;
/**
* <p>
* Utility for formatting and parsing coin values to and from human readable form.
* </p>
*
* <p>
* MonetaryFormat instances are immutable. Invoking a configuration method has no effect on the receiving instance; you
* must store and use the new instance it returns, instead. Instances are thread safe, so they may be stored safely as
* static constants.
* </p>
*/
public class DogecoinMonetaryFormat extends org.bitcoinj.utils.MonetaryFormat {
/** Standard format for the BTC denomination. */
public static final MonetaryFormat BTC = new MonetaryFormat().shift(0).minDecimals(2).repeatOptionalDecimals(2, 3);
/** Standard format for the mBTC denomination. */
public static final MonetaryFormat MBTC = new MonetaryFormat().shift(3).minDecimals(2).optionalDecimals(2);
/** Standard format for the µBTC denomination. */
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 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;
private final char zeroDigit;
private final char decimalMark;
private final int minDecimals;
private final List<Integer> decimalGroups;
private final int shift;
private final RoundingMode roundingMode;
private final Map<Integer, String> codes;
private final char codeSeparator;
private final boolean codePrefixed;
private static final String DECIMALS_PADDING = "0000000000000000"; // a few more than necessary for Bitcoin
/**
* Set character to prefix negative values.
*/
public MonetaryFormat negativeSign(char negativeSign) {
checkArgument(!Character.isDigit(negativeSign));
checkArgument(negativeSign > 0);
if (negativeSign == this.negativeSign)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set character to prefix positive values. A zero value means no sign is used in this case. For parsing, a missing
* sign will always be interpreted as if the positive sign was used.
*/
public MonetaryFormat positiveSign(char positiveSign) {
checkArgument(!Character.isDigit(positiveSign));
if (positiveSign == this.positiveSign)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set character range to use for representing digits. It starts with the specified character representing zero.
*/
public MonetaryFormat digits(char zeroDigit) {
if (zeroDigit == this.zeroDigit)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set character to use as the decimal mark. If the formatted value does not have any decimals, no decimal mark is
* used either.
*/
public MonetaryFormat decimalMark(char decimalMark) {
checkArgument(!Character.isDigit(decimalMark));
checkArgument(decimalMark > 0);
if (decimalMark == this.decimalMark)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set minimum number of decimals to use for formatting. If the value precision exceeds all decimals specified
* (including additional decimals specified by {@link #optionalDecimals(int...)} or
* {@link #repeatOptionalDecimals(int, int)}), the value will be rounded. This configuration is not relevant for
* parsing.
*/
public MonetaryFormat minDecimals(int minDecimals) {
if (minDecimals == this.minDecimals)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* <p>
* Set additional groups of decimals to use after the minimum decimals, if they are useful for expressing precision.
* Each value is a number of decimals in that group. If the value precision exceeds all decimals specified
* (including minimum decimals), the value will be rounded. This configuration is not relevant for parsing.
* </p>
*
* <p>
* For example, if you pass <tt>4,2</tt> it will add four decimals to your formatted string if needed, and then add
* another two decimals if needed. At this point, rather than adding further decimals the value will be rounded.
* </p>
*
* @param groups
* any number numbers of decimals, one for each group
*/
public MonetaryFormat optionalDecimals(int... groups) {
List<Integer> decimalGroups = new ArrayList<Integer>(groups.length);
for (int group : groups)
decimalGroups.add(group);
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* <p>
* Set repeated additional groups of decimals to use after the minimum decimals, if they are useful for expressing
* precision. If the value precision exceeds all decimals specified (including minimum decimals), the value will be
* rounded. This configuration is not relevant for parsing.
* </p>
*
* <p>
* For example, if you pass <tt>1,8</tt> it will up to eight decimals to your formatted string if needed. After
* these have been used up, rather than adding further decimals the value will be rounded.
* </p>
*
* @param decimals
* value of the group to be repeated
* @param repetitions
* number of repetitions
*/
public MonetaryFormat repeatOptionalDecimals(int decimals, int repetitions) {
checkArgument(repetitions >= 0);
List<Integer> decimalGroups = new ArrayList<Integer>(repetitions);
for (int i = 0; i < repetitions; i++)
decimalGroups.add(decimals);
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set number of digits to shift the decimal separator to the right, coming from the standard BTC notation that was
* common pre-2014. Note this will change the currency code if enabled.
*/
public MonetaryFormat shift(int shift) {
if (shift == this.shift)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Set rounding mode to use when it becomes necessary.
*/
public MonetaryFormat roundingMode(RoundingMode roundingMode) {
if (roundingMode == this.roundingMode)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Don't display currency code when formatting. This configuration is not relevant for parsing.
*/
public MonetaryFormat noCode() {
if (codes == null)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, null, codeSeparator, codePrefixed);
}
/**
* Configure currency code for given decimal separator shift. This configuration is not relevant for parsing.
*
* @param codeShift
* decimal separator shift, see {@link #shift()}
* @param code
* currency code
*/
public MonetaryFormat code(int codeShift, String code) {
checkArgument(codeShift >= 0);
Map<Integer, String> codes = new HashMap<Integer, String>();
if (this.codes != null)
codes.putAll(this.codes);
codes.put(codeShift, code);
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Separator between currency code and formatted value. This configuration is not relevant for parsing.
*/
public MonetaryFormat codeSeparator(char codeSeparator) {
checkArgument(!Character.isDigit(codeSeparator));
checkArgument(codeSeparator > 0);
if (codeSeparator == this.codeSeparator)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
/**
* Prefix formatted output by currency code. This configuration is not relevant for parsing.
*/
public MonetaryFormat prefixCode() {
if (codePrefixed)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, true);
}
/**
* Postfix formatted output with currency code. This configuration is not relevant for parsing.
*/
public MonetaryFormat postfixCode() {
if (!codePrefixed)
return this;
else
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, false);
}
/**
* Configure this instance with values from a {@link Locale}.
*/
public MonetaryFormat withLocale(Locale locale) {
DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale);
char negativeSign = dfs.getMinusSign();
char zeroDigit = dfs.getZeroDigit();
char decimalMark = dfs.getMonetaryDecimalSeparator();
return new MonetaryFormat(negativeSign, positiveSign, zeroDigit, decimalMark, minDecimals, decimalGroups,
shift, roundingMode, codes, codeSeparator, codePrefixed);
}
public MonetaryFormat() {
// defaults
this.negativeSign = '-';
this.positiveSign = 0; // none
this.zeroDigit = '0';
this.decimalMark = '.';
this.minDecimals = 2;
this.decimalGroups = null;
this.shift = 0;
this.roundingMode = RoundingMode.HALF_UP;
this.codes = new HashMap<Integer, String>();
this.codes.put(0, CODE_BTC);
this.codes.put(3, CODE_MBTC);
this.codes.put(6, CODE_UBTC);
this.codeSeparator = ' ';
this.codePrefixed = true;
}
private MonetaryFormat(char negativeSign, char positiveSign, char zeroDigit, char decimalMark, int minDecimals,
List<Integer> decimalGroups, int shift, RoundingMode roundingMode, Map<Integer, String> codes,
char codeSeparator, boolean codePrefixed) {
this.negativeSign = negativeSign;
this.positiveSign = positiveSign;
this.zeroDigit = zeroDigit;
this.decimalMark = decimalMark;
this.minDecimals = minDecimals;
this.decimalGroups = decimalGroups;
this.shift = shift;
this.roundingMode = roundingMode;
this.codes = codes;
this.codeSeparator = codeSeparator;
this.codePrefixed = codePrefixed;
}
/**
* Format the given monetary value to a human readable form.
*/
public CharSequence format(Monetary monetary) {
// preparation
int maxDecimals = minDecimals;
if (decimalGroups != null)
for (int group : decimalGroups)
maxDecimals += group;
checkState(maxDecimals <= monetary.smallestUnitExponent());
// rounding
long satoshis = Math.abs(monetary.getValue());
long precisionDivisor = checkedPow(10, monetary.smallestUnitExponent() - shift - maxDecimals);
satoshis = checkedMultiply(divide(satoshis, precisionDivisor, roundingMode), precisionDivisor);
// shifting
long shiftDivisor = checkedPow(10, monetary.smallestUnitExponent() - shift);
long numbers = satoshis / shiftDivisor;
long decimals = satoshis % shiftDivisor;
// formatting
String decimalsStr = String.format(Locale.US, "%0" + (monetary.smallestUnitExponent() - shift) + "d", decimals);
StringBuilder str = new StringBuilder(decimalsStr);
while (str.length() > minDecimals && str.charAt(str.length() - 1) == '0')
str.setLength(str.length() - 1); // trim trailing zero
int i = minDecimals;
if (decimalGroups != null) {
for (int group : decimalGroups) {
if (str.length() > i && str.length() < i + group) {
while (str.length() < i + group)
str.append('0');
break;
}
i += group;
}
}
if (str.length() > 0)
str.insert(0, decimalMark);
str.insert(0, numbers);
if (monetary.getValue() < 0)
str.insert(0, negativeSign);
else if (positiveSign != 0)
str.insert(0, positiveSign);
if (codes != null) {
if (codePrefixed) {
str.insert(0, codeSeparator);
str.insert(0, code());
} else {
str.append(codeSeparator);
str.append(code());
}
}
// Convert to non-arabic digits.
if (zeroDigit != '0') {
int offset = zeroDigit - '0';
for (int d = 0; d < str.length(); d++) {
char c = str.charAt(d);
if (Character.isDigit(c))
str.setCharAt(d, (char) (c + offset));
}
}
return str;
}
/**
* Parse a human readable coin value to a {@link org.altcoinj.core.Coin} instance.
*
* @throws NumberFormatException
* if the string cannot be parsed for some reason
*/
public Coin parse(String str) throws NumberFormatException {
return Coin.valueOf(parseValue(str, Coin.SMALLEST_UNIT_EXPONENT));
}
/**
* Parse a human readable fiat value to a {@link org.altcoinj.core.Fiat} instance.
*
* @throws NumberFormatException
* if the string cannot be parsed for some reason
*/
public Fiat parseFiat(String currencyCode, String str) throws NumberFormatException {
return Fiat.valueOf(currencyCode, parseValue(str, Fiat.SMALLEST_UNIT_EXPONENT));
}
private long parseValue(String str, int smallestUnitExponent) {
checkState(DECIMALS_PADDING.length() >= smallestUnitExponent);
if (str.isEmpty())
throw new NumberFormatException("empty string");
char first = str.charAt(0);
if (first == negativeSign || first == positiveSign)
str = str.substring(1);
String numbers;
String decimals;
int decimalMarkIndex = str.indexOf(decimalMark);
if (decimalMarkIndex != -1) {
numbers = str.substring(0, decimalMarkIndex);
decimals = (str + DECIMALS_PADDING).substring(decimalMarkIndex + 1);
if (decimals.indexOf(decimalMark) != -1)
throw new NumberFormatException("more than one decimal mark");
} else {
numbers = str;
decimals = DECIMALS_PADDING;
}
String satoshis = numbers + decimals.substring(0, smallestUnitExponent - shift);
for (char c : satoshis.toCharArray())
if (!Character.isDigit(c))
throw new NumberFormatException("illegal character: " + c);
long value = Long.parseLong(satoshis); // Non-arabic digits allowed here.
if (first == negativeSign)
value = -value;
return value;
}
/**
* Get currency code that will be used for current shift.
*/
public String code() {
if (codes == null)
return null;
String code = codes.get(shift);
if (code == null)
throw new NumberFormatException("missing code for shift: " + shift);
return code;
}
}

View File

@ -1,341 +0,0 @@
/*
* Copyright 2012 Google Inc.
* Copyright 2012 Matt Corallo.
*
* 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 com.google.common.collect.Lists;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.script.Script;
import com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.utils.BlockFileLoader;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.wallet.WalletTransaction;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import static com.dogecoin.dogecoinj.core.Coin.FIFTY_COINS;
import static org.junit.Assert.*;
/**
* We don't do any wallet tests here, we leave that to {@link ChainSplitTest}
*/
public abstract class AbstractFullPrunedBlockChainTest
{
private static final Logger log = LoggerFactory.getLogger(AbstractFullPrunedBlockChainTest.class);
protected NetworkParameters params;
protected FullPrunedBlockChain chain;
protected FullPrunedBlockStore store;
@Before
public void setUp() throws Exception {
BriefLogFormatter.init();
params = new UnitTestParams() {
@Override public int getInterval() {
return 10000;
}
};
}
public abstract FullPrunedBlockStore createStore(NetworkParameters params, int blockCount)
throws BlockStoreException;
public abstract void resetStore(FullPrunedBlockStore store) throws BlockStoreException;
@Test
public void testGeneratedChain() throws Exception {
// Tests various test cases from FullBlockTestGenerator
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
RuleList blockList = generator.getBlocksToTest(false, false, null);
store = createStore(params, blockList.maximumReorgBlockCount);
chain = new FullPrunedBlockChain(params, store);
for (Rule rule : blockList.list) {
if (!(rule instanceof FullBlockTestGenerator.BlockAndValidity))
continue;
FullBlockTestGenerator.BlockAndValidity block = (FullBlockTestGenerator.BlockAndValidity) rule;
log.info("Testing rule " + block.ruleName + " with block hash " + block.block.getHash());
boolean threw = false;
try {
if (chain.add(block.block) != block.connects) {
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block " + block.ruleName);
throw e;
}
if (block.connects) {
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block " + block.ruleName);
fail();
}
if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block " + block.ruleName);
fail();
}
if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("New block head didn't match the correct height after block " + block.ruleName);
fail();
}
}
try {
store.close();
} catch (Exception e) {}
}
@Test
public void skipScripts() throws Exception {
store = createStore(params, 10);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0);
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
}
rollingBlock = rollingBlock.createNextBlock(null);
Transaction t = new Transaction(params);
t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {}));
TransactionInput input = t.addInput(spendableOutput);
// Invalid script.
input.setScriptBytes(new byte[]{});
rollingBlock.addTransaction(t);
rollingBlock.solve();
chain.setRunScripts(false);
try {
chain.add(rollingBlock);
} catch (VerificationException e) {
fail();
}
try {
store.close();
} catch (Exception e) {}
}
@Test
public void testFinalizedBlocks() throws Exception {
final int UNDOABLE_BLOCKS_STORED = 10;
store = createStore(params, UNDOABLE_BLOCKS_STORED);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, rollingBlock.getTransactions().get(0).getHash());
byte[] spendableOutputScriptPubKey = rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes();
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
}
WeakReference<UTXO> out = new WeakReference<UTXO>
(store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex()));
rollingBlock = rollingBlock.createNextBlock(null);
Transaction t = new Transaction(params);
// Entirely invalid scriptPubKey
t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[]{}));
t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
rollingBlock.addTransaction(t);
rollingBlock.solve();
chain.add(rollingBlock);
WeakReference<StoredUndoableBlock> undoBlock = new WeakReference<StoredUndoableBlock>(store.getUndoBlock(rollingBlock.getHash()));
StoredUndoableBlock storedUndoableBlock = undoBlock.get();
assertNotNull(storedUndoableBlock);
assertNull(storedUndoableBlock.getTransactions());
WeakReference<TransactionOutputChanges> changes = new WeakReference<TransactionOutputChanges>(storedUndoableBlock.getTxOutChanges());
assertNotNull(changes.get());
storedUndoableBlock = null; // Blank the reference so it can be GCd.
// Create a chain longer than UNDOABLE_BLOCKS_STORED
for (int i = 0; i < UNDOABLE_BLOCKS_STORED; i++) {
rollingBlock = rollingBlock.createNextBlock(null);
chain.add(rollingBlock);
}
// Try to get the garbage collector to run
System.gc();
assertNull(undoBlock.get());
assertNull(changes.get());
assertNull(out.get());
try {
store.close();
} catch (Exception e) {}
}
@Test
public void testFirst100KBlocks() throws Exception {
NetworkParameters params = MainNetParams.get();
File blockFile = new File(getClass().getResource("first-100k-blocks.dat").getFile());
BlockFileLoader loader = new BlockFileLoader(params, Arrays.asList(blockFile));
store = createStore(params, 10);
resetStore(store);
chain = new FullPrunedBlockChain(params, store);
for (Block block : loader)
chain.add(block);
try {
store.close();
} catch (Exception e) {}
}
@Test
public void testGetOpenTransactionOutputs() throws Exception {
final int UNDOABLE_BLOCKS_STORED = 10;
store = createStore(params, UNDOABLE_BLOCKS_STORED);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
Transaction transaction = rollingBlock.getTransactions().get(0);
TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash());
byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes();
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
}
rollingBlock = rollingBlock.createNextBlock(null);
// Create bitcoin spend of 1 BTC.
ECKey toKey = new ECKey();
Coin amount = Coin.valueOf(100000000);
Address address = new Address(params, toKey.getPubKeyHash());
Coin totalAmount = Coin.ZERO;
Transaction t = new Transaction(params);
t.addOutput(new TransactionOutput(params, t, amount, toKey));
t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
rollingBlock.addTransaction(t);
rollingBlock.solve();
chain.add(rollingBlock);
totalAmount = totalAmount.add(amount);
List<UTXO> outputs = store.getOpenTransactionOutputs(Lists.newArrayList(address));
assertNotNull(outputs);
assertEquals("Wrong Number of Outputs", 1, outputs.size());
UTXO output = outputs.get(0);
assertEquals("The address is not equal", address.toString(), output.getAddress());
assertEquals("The amount is not equal", totalAmount, output.getValue());
outputs = null;
output = null;
try {
store.close();
} catch (Exception e) {}
}
@Test
public void testUTXOProviderWithWallet() throws Exception {
final int UNDOABLE_BLOCKS_STORED = 10;
store = createStore(params, UNDOABLE_BLOCKS_STORED);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output.
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
Transaction transaction = rollingBlock.getTransactions().get(0);
TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash());
byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes();
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
}
rollingBlock = rollingBlock.createNextBlock(null);
// Create 1 BTC spend to a key in this wallet (to ourselves).
Wallet wallet = new Wallet(params);
assertEquals("Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
assertEquals("Estimated balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
wallet.setUTXOProvider(store);
ECKey toKey = wallet.freshReceiveKey();
Coin amount = Coin.valueOf(100000000);
Transaction t = new Transaction(params);
t.addOutput(new TransactionOutput(params, t, amount, toKey));
t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
rollingBlock.addTransaction(t);
rollingBlock.solve();
chain.add(rollingBlock);
// Create another spend of 1/2 the value of BTC we have available using the wallet (store coin selector).
ECKey toKey2 = new ECKey();
Coin amount2 = amount.divide(2);
Address address2 = new Address(params, toKey2.getPubKeyHash());
Wallet.SendRequest req = Wallet.SendRequest.to(address2, amount2);
wallet.completeTx(req);
wallet.commitTx(req.tx);
Coin fee = req.fee;
// There should be one pending tx (our spend).
assertEquals("Wrong number of PENDING.4", 1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
Coin totalPendingTxAmount = Coin.ZERO;
for (Transaction tx : wallet.getPendingTransactions()) {
totalPendingTxAmount = totalPendingTxAmount.add(tx.getValueSentToMe(wallet));
}
// The availbale balance should be the 0 (as we spent the 1 BTC that's pending) and estimated should be 1/2 - fee BTC
assertEquals("Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
assertEquals("Estimated balance is incorrect", amount2.subtract(fee), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
assertEquals("Pending tx amount is incorrect", amount2.subtract(fee), totalPendingTxAmount);
try {
store.close();
} catch (Exception e) {}
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2011 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Before;
import org.junit.Test;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class AlertMessageTest {
private static final byte[] TEST_KEY_PRIV = HEX.decode("6421e091445ade4b24658e96aa60959ce800d8ea9e7bd8613335aa65ba8d840b");
private NetworkParameters params;
@Before
public void setUp() throws Exception {
final ECKey key = ECKey.fromPrivate(TEST_KEY_PRIV);
params = new UnitTestParams() {
@Override
public byte[] getAlertSigningKey() {
return key.getPubKey();
}
};
}
@Test
public void deserialize() throws Exception {
// A CAlert taken from the reference implementation.
// TODO: This does not check the subVer or set fields. Support proper version matching.
final byte[] payload = HEX.decode("5c010000004544eb4e000000004192ec4e00000000eb030000e9030000000000000048ee00000088130000002f43416c6572742073797374656d20746573743a2020202020202020207665722e302e352e3120617661696c61626c6500473045022100ec799908c008b272d5e5cd5a824abaaac53d210cc1fa517d8e22a701ecdb9e7002206fa1e7e7c251d5ba0d7c1fe428fc1870662f2927531d1cad8d4581b45bc4f8a7");
AlertMessage alert = new AlertMessage(params, payload);
assertEquals(1324041285, alert.getRelayUntil().getTime() / 1000);
assertEquals(1324126785, alert.getExpiration().getTime() / 1000);
assertEquals(1003, alert.getId());
assertEquals(1001, alert.getCancel());
assertEquals(0, alert.getMinVer());
assertEquals(61000, alert.getMaxVer());
assertEquals(5000, alert.getPriority());
assertEquals("CAlert system test: ver.0.5.1 available", alert.getStatusBar());
assertTrue(alert.isSignatureValid());
}
}

View File

@ -1,91 +0,0 @@
/**
* 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 junit.framework.TestCase;
import org.junit.Test;
import java.math.BigInteger;
import java.util.Arrays;
public class Base58Test extends TestCase {
@Test
public void testEncode() throws Exception {
byte[] testbytes = "Hello World".getBytes();
assertEquals("JxF12TrwUP45BMd", Base58.encode(testbytes));
BigInteger bi = BigInteger.valueOf(3471844090L);
assertEquals("16Ho7Hs", Base58.encode(bi.toByteArray()));
byte[] zeroBytes1 = new byte[1];
assertEquals("1", Base58.encode(zeroBytes1));
byte[] zeroBytes7 = new byte[7];
assertEquals("1111111", Base58.encode(zeroBytes7));
// test empty encode
assertEquals("", Base58.encode(new byte[0]));
}
@Test
public void testDecode() throws Exception {
byte[] testbytes = "Hello World".getBytes();
byte[] actualbytes = Base58.decode("JxF12TrwUP45BMd");
assertTrue(new String(actualbytes), Arrays.equals(testbytes, actualbytes));
assertTrue("1", Arrays.equals(Base58.decode("1"), new byte[1]));
assertTrue("1111", Arrays.equals(Base58.decode("1111"), new byte[4]));
try {
Base58.decode("This isn't valid base58");
fail();
} catch (AddressFormatException e) {
// expected
}
Base58.decodeChecked("4stwEBjT6FYyVV");
// Checksum should fail.
try {
Base58.decodeChecked("4stwEBjT6FYyVW");
fail();
} catch (AddressFormatException e) {
// expected
}
// Input is too short.
try {
Base58.decodeChecked("4s");
fail();
} catch (AddressFormatException e) {
// expected
}
// Test decode of empty String.
assertEquals(0, Base58.decode("").length);
// Now check we can correctly decode the case where the high bit of the first byte is not zero, so BigInteger
// sign extends. Fix for a bug that stopped us parsing keys exported using sipas patch.
Base58.decodeChecked("93VYUMzRG9DdbRP72uQXjaWibbQwygnvaCu9DumcqDjGybD864T");
}
@Test
public void testDecodeToBigInteger() throws AddressFormatException {
byte[] input = Base58.decode("129");
assertEquals(new BigInteger(1, input), Base58.decodeToBigInteger("129"));
}
}

View File

@ -1,288 +0,0 @@
/**
* Copyright 2011 Noa Resare
* 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.MainNetParams;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.*;
public class BitcoinSerializerTest {
private final byte[] addrMessage = HEX.decode("f9beb4d96164647200000000000000001f000000" +
"ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d");
private final byte[] txMessage = HEX.withSeparator(" ", 2).decode(
"f9 be b4 d9 74 78 00 00 00 00 00 00 00 00 00 00" +
"02 01 00 00 e2 93 cd be 01 00 00 00 01 6d bd db" +
"08 5b 1d 8a f7 51 84 f0 bc 01 fa d5 8d 12 66 e9" +
"b6 3b 50 88 19 90 e4 b4 0d 6a ee 36 29 00 00 00" +
"00 8b 48 30 45 02 21 00 f3 58 1e 19 72 ae 8a c7" +
"c7 36 7a 7a 25 3b c1 13 52 23 ad b9 a4 68 bb 3a" +
"59 23 3f 45 bc 57 83 80 02 20 59 af 01 ca 17 d0" +
"0e 41 83 7a 1d 58 e9 7a a3 1b ae 58 4e de c2 8d" +
"35 bd 96 92 36 90 91 3b ae 9a 01 41 04 9c 02 bf" +
"c9 7e f2 36 ce 6d 8f e5 d9 40 13 c7 21 e9 15 98" +
"2a cd 2b 12 b6 5d 9b 7d 59 e2 0a 84 20 05 f8 fc" +
"4e 02 53 2e 87 3d 37 b9 6f 09 d6 d4 51 1a da 8f" +
"14 04 2f 46 61 4a 4c 70 c0 f1 4b ef f5 ff ff ff" +
"ff 02 40 4b 4c 00 00 00 00 00 19 76 a9 14 1a a0" +
"cd 1c be a6 e7 45 8a 7a ba d5 12 a9 d9 ea 1a fb" +
"22 5e 88 ac 80 fa e9 c7 00 00 00 00 19 76 a9 14" +
"0e ab 5b ea 43 6a 04 84 cf ab 12 48 5e fd a0 b7" +
"8b 4e cc 52 88 ac 00 00 00 00");
@Test
public void testAddr() throws Exception {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get());
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#addr
AddressMessage a = (AddressMessage)bs.deserialize(ByteBuffer.wrap(addrMessage));
assertEquals(1, a.getAddresses().size());
PeerAddress pa = a.getAddresses().get(0);
assertEquals(8333, pa.getPort());
assertEquals("10.0.0.1", pa.getAddr().getHostAddress());
ByteArrayOutputStream bos = new ByteArrayOutputStream(addrMessage.length);
bs.serialize(a, bos);
assertEquals(31, a.getMessageSize());
a.addAddress(new PeerAddress(InetAddress.getLocalHost()));
assertEquals(61, a.getMessageSize());
a.removeAddress(0);
assertEquals(31, a.getMessageSize());
//this wont be true due to dynamic timestamps.
//assertTrue(LazyParseByteCacheTest.arrayContains(bos.toByteArray(), addrMessage));
}
@Test
public void testLazyParsing() throws Exception {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get(), true, false);
Transaction tx = (Transaction)bs.deserialize(ByteBuffer.wrap(txMessage));
assertNotNull(tx);
assertEquals(false, tx.isParsed());
assertEquals(true, tx.isCached());
tx.getInputs();
assertEquals(true, tx.isParsed());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
}
@Test
public void testCachedParsing() throws Exception {
testCachedParsing(true);
testCachedParsing(false);
}
private void testCachedParsing(boolean lazy) throws Exception {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get(), lazy, true);
//first try writing to a fields to ensure uncaching and children are not affected
Transaction tx = (Transaction)bs.deserialize(ByteBuffer.wrap(txMessage));
assertNotNull(tx);
assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached());
tx.setLockTime(1);
//parent should have been uncached
assertEquals(false, tx.isCached());
//child should remain cached.
assertEquals(true, tx.getInputs().get(0).isCached());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
assertEquals(true, !Arrays.equals(txMessage, bos.toByteArray()));
//now try writing to a child to ensure uncaching is propagated up to parent but not to siblings
tx = (Transaction)bs.deserialize(ByteBuffer.wrap(txMessage));
assertNotNull(tx);
assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached());
tx.getInputs().get(0).setSequenceNumber(1);
//parent should have been uncached
assertEquals(false, tx.isCached());
//so should child
assertEquals(false, tx.getInputs().get(0).isCached());
bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
assertEquals(true, !Arrays.equals(txMessage, bos.toByteArray()));
//deserialize/reserialize to check for equals.
tx = (Transaction)bs.deserialize(ByteBuffer.wrap(txMessage));
assertNotNull(tx);
assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached());
bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
//deserialize/reserialize to check for equals. Set a field to it's existing value to trigger uncache
tx = (Transaction)bs.deserialize(ByteBuffer.wrap(txMessage));
assertNotNull(tx);
assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached());
tx.getInputs().get(0).setSequenceNumber(tx.getInputs().get(0).getSequenceNumber());
bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
}
/**
* Get 1 header of the block number 1 (the first one is 0) in the chain
*/
@Test
public void testHeaders1() throws Exception {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get());
HeadersMessage hm = (HeadersMessage) bs.deserialize(ByteBuffer.wrap(HEX.decode("f9beb4d9686561" +
"646572730000000000520000005d4fab8101010000006fe28c0ab6f1b372c1a6a246ae6" +
"3f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677b" +
"a1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e3629900")));
// The first block after the genesis
// http://blockexplorer.com/b/1
Block block = hm.getBlockHeaders().get(0);
String hash = block.getHashAsString();
assertEquals(hash, "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048");
assertNull(block.transactions);
assertEquals(Utils.HEX.encode(block.getMerkleRoot().getBytes()),
"0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
}
@Test
/**
* Get 6 headers of blocks 1-6 in the chain
*/
public void testHeaders2() throws Exception {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get());
HeadersMessage hm = (HeadersMessage) bs.deserialize(ByteBuffer.wrap(HEX.decode("f9beb4d96865616465" +
"72730000000000e701000085acd4ea06010000006fe28c0ab6f1b372c1a6a246ae63f74f931e" +
"8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1c" +
"db606e857233e0e61bc6649ffff001d01e3629900010000004860eb18bf1b1620e37e9490fc8a" +
"427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36" +
"ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610001000000bddd99ccfda39da1b108ce1" +
"a5d70038d0a967bacb68b6b63065f626a0000000044f672226090d85db9a9f2fbfe5f0f9609b387" +
"af7be5b7fbb7a1767c831c9e995dbe6649ffff001d05e0ed6d00010000004944469562ae1c2c74" +
"d9a535e00b6f3e40ffbad4f2fda3895501b582000000007a06ea98cd40ba2e3288262b28638cec" +
"5337c1456aaf5eedc8e9e5a20f062bdf8cc16649ffff001d2bfee0a9000100000085144a84488e" +
"a88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023" +
"370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e4770001000000fc33f5" +
"96f822a0a1951ffdbf2a897b095636ad871707bf5d3162729b00000000379dfb96a5ea8c81700ea4" +
"ac6b97ae9a9312b2d4301a29580e924ee6761a2520adc46649ffff001d189c4c9700")));
int nBlocks = hm.getBlockHeaders().size();
assertEquals(nBlocks, 6);
// index 0 block is the number 1 block in the block chain
// http://blockexplorer.com/b/1
Block zeroBlock = hm.getBlockHeaders().get(0);
String zeroBlockHash = zeroBlock.getHashAsString();
assertEquals("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048",
zeroBlockHash);
assertEquals(zeroBlock.getNonce(), 2573394689L);
Block thirdBlock = hm.getBlockHeaders().get(3);
String thirdBlockHash = thirdBlock.getHashAsString();
// index 3 block is the number 4 block in the block chain
// http://blockexplorer.com/b/4
assertEquals("000000004ebadb55ee9096c9a2f8880e09da59c0d68b1c228da88e48844a1485",
thirdBlockHash);
assertEquals(thirdBlock.getNonce(), 2850094635L);
}
@Test
public void testBitcoinPacketHeader() {
try {
new BitcoinSerializer.BitcoinPacketHeader(ByteBuffer.wrap(new byte[]{0}));
fail();
} catch (BufferUnderflowException e) {
}
// Message with a Message size which is 1 too big, in little endian format.
byte[] wrongMessageLength = HEX.decode("000000000000000000000000010000020000000000");
try {
new BitcoinSerializer.BitcoinPacketHeader(ByteBuffer.wrap(wrongMessageLength));
fail();
} catch (ProtocolException e) {
// expected
}
}
@Test
public void testSeekPastMagicBytes() {
// Fail in another way, there is data in the stream but no magic bytes.
byte[] brokenMessage = HEX.decode("000000");
try {
new BitcoinSerializer(MainNetParams.get()).seekPastMagicBytes(ByteBuffer.wrap(brokenMessage));
fail();
} catch (BufferUnderflowException e) {
// expected
}
}
@Test
/**
* Tests serialization of an unknown message.
*/
public void testSerializeUnknownMessage() {
BitcoinSerializer bs = new BitcoinSerializer(MainNetParams.get());
UnknownMessage a = new UnknownMessage();
ByteArrayOutputStream bos = new ByteArrayOutputStream(addrMessage.length);
try {
bs.serialize(a, bos);
fail();
} catch (Throwable e) {
}
}
/**
* Unknown message for testSerializeUnknownMessage.
*/
class UnknownMessage extends Message {
@Override
void parse() throws ProtocolException {
}
@Override
protected void parseLite() throws ProtocolException {
}
}
}

View File

@ -1,372 +0,0 @@
/*
* Copyright 2012 Matt Corallo.
* 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 com.dogecoin.dogecoinj.core;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import com.dogecoin.dogecoinj.net.NioClient;
import com.dogecoin.dogecoinj.params.RegTestParams;
import com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.H2FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.MemoryBlockStore;
import com.dogecoin.dogecoinj.utils.BlockFileLoader;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.utils.Threading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A tool for comparing the blocks which are accepted/rejected by bitcoind/dogecoinj
* It is designed to run as a testnet-in-a-box network between a single bitcoind node and dogecoinj
* It is not an automated unit-test because it requires a bit more set-up...read comments below
*/
public class BitcoindComparisonTool {
private static final Logger log = LoggerFactory.getLogger(BitcoindComparisonTool.class);
private static NetworkParameters params;
private static FullPrunedBlockStore store;
private static FullPrunedBlockChain chain;
private static Sha256Hash bitcoindChainHead;
private static volatile InventoryMessage mostRecentInv = null;
static class BlockWrapper {
public Block block;
}
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
System.out.println("USAGE: bitcoinjBlockStoreLocation runExpensiveTests(1/0) [port=18444]");
boolean runExpensiveTests = args.length > 1 && Integer.parseInt(args[1]) == 1;
params = RegTestParams.get();
File blockFile = File.createTempFile("testBlocks", ".dat");
blockFile.deleteOnExit();
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
final RuleList blockList = generator.getBlocksToTest(false, runExpensiveTests, blockFile);
final Map<Sha256Hash, Block> preloadedBlocks = new HashMap<Sha256Hash, Block>();
final Iterator<Block> blocks = new BlockFileLoader(params, Arrays.asList(blockFile));
try {
store = new H2FullPrunedBlockStore(params, args.length > 0 ? args[0] : "BitcoindComparisonTool", blockList.maximumReorgBlockCount);
((H2FullPrunedBlockStore)store).resetStore();
//store = new MemoryFullPrunedBlockStore(params, blockList.maximumReorgBlockCount);
chain = new FullPrunedBlockChain(params, store);
} catch (BlockStoreException e) {
e.printStackTrace();
System.exit(1);
}
VersionMessage ver = new VersionMessage(params, 42);
ver.appendToSubVer("BlockAcceptanceComparisonTool", "1.1", null);
ver.localServices = VersionMessage.NODE_NETWORK;
final Peer bitcoind = new Peer(params, ver, new BlockChain(params, new MemoryBlockStore(params)), new PeerAddress(InetAddress.getLocalHost()));
Preconditions.checkState(bitcoind.getVersionMessage().hasBlockChain());
final BlockWrapper currentBlock = new BlockWrapper();
final Set<Sha256Hash> blocksRequested = Collections.synchronizedSet(new HashSet<Sha256Hash>());
final Set<Sha256Hash> blocksPendingSend = Collections.synchronizedSet(new HashSet<Sha256Hash>());
final AtomicInteger unexpectedInvs = new AtomicInteger(0);
final SettableFuture<Void> connectedFuture = SettableFuture.create();
bitcoind.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
if (!peer.getPeerVersionMessage().subVer.contains("Satoshi")) {
System.out.println();
System.out.println("************************************************************************************************************************\n" +
"WARNING: You appear to be using this to test an alternative implementation with full validation rules. You should go\n" +
"think hard about what you're doing. Seriously, no one has gotten even close to correctly reimplementing Bitcoin\n" +
"consensus rules, despite serious investment in trying. It is a huge task and the slightest difference is a huge bug.\n" +
"Instead, go work on making Bitcoin Core consensus rules a shared library and use that. Seriously, you wont get it right,\n" +
"and starting with this tester as a way to try to do so will simply end in pain and lost coins.\n" +
"************************************************************************************************************************");
System.out.println();
System.out.println("Giving you 30 seconds to think about the above warning...");
Uninterruptibles.sleepUninterruptibly(30, TimeUnit.SECONDS);
}
log.info("bitcoind connected");
// Make sure bitcoind has no blocks
bitcoind.setDownloadParameters(0, false);
bitcoind.startBlockChainDownload();
connectedFuture.set(null);
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
log.error("bitcoind node disconnected!");
System.exit(1);
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
if (m instanceof HeadersMessage) {
if (!((HeadersMessage) m).getBlockHeaders().isEmpty()) {
Block b = Iterables.getLast(((HeadersMessage) m).getBlockHeaders());
log.info("Got header from bitcoind " + b.getHashAsString());
bitcoindChainHead = b.getHash();
} else
log.info("Got empty header message from bitcoind");
return null;
} else if (m instanceof Block) {
log.error("bitcoind sent us a block it already had, make sure bitcoind has no blocks!");
System.exit(1);
} else if (m instanceof GetDataMessage) {
for (InventoryItem item : ((GetDataMessage) m).items)
if (item.type == InventoryItem.Type.Block) {
log.info("Requested " + item.hash);
if (currentBlock.block.getHash().equals(item.hash))
bitcoind.sendMessage(currentBlock.block);
else {
Block nextBlock = preloadedBlocks.get(item.hash);
if (nextBlock != null)
bitcoind.sendMessage(nextBlock);
else {
blocksPendingSend.add(item.hash);
log.info("...which we will not provide yet");
}
}
blocksRequested.add(item.hash);
}
return null;
} else if (m instanceof GetHeadersMessage) {
try {
if (currentBlock.block == null) {
log.info("Got a request for a header before we had even begun processing blocks!");
return null;
}
LinkedList<Block> headers = new LinkedList<Block>();
Block it = blockList.hashHeaderMap.get(currentBlock.block.getHash());
while (it != null) {
headers.addFirst(it);
it = blockList.hashHeaderMap.get(it.getPrevBlockHash());
}
LinkedList<Block> sendHeaders = new LinkedList<Block>();
boolean found = false;
for (Sha256Hash hash : ((GetHeadersMessage) m).getLocator()) {
for (Block b : headers) {
if (found) {
sendHeaders.addLast(b);
log.info("Sending header (" + b.getPrevBlockHash() + ") -> " + b.getHash());
if (b.getHash().equals(((GetHeadersMessage) m).getStopHash()))
break;
} else if (b.getHash().equals(hash)) {
log.info("Found header " + b.getHashAsString());
found = true;
}
}
if (found)
break;
}
if (!found)
sendHeaders = headers;
bitcoind.sendMessage(new HeadersMessage(params, sendHeaders));
InventoryMessage i = new InventoryMessage(params);
for (Block b : sendHeaders)
i.addBlock(b);
bitcoind.sendMessage(i);
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
} else if (m instanceof InventoryMessage) {
if (mostRecentInv != null) {
log.error("Got an inv when we weren't expecting one");
unexpectedInvs.incrementAndGet();
}
mostRecentInv = (InventoryMessage) m;
}
return m;
}
}, Threading.SAME_THREAD);
bitcoindChainHead = params.getGenesisBlock().getHash();
// bitcoind MUST be on localhost or we will get banned as a DoSer
new NioClient(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), args.length > 2 ? Integer.parseInt(args[2]) : params.getPort()), bitcoind, 1000);
connectedFuture.get();
ArrayList<Sha256Hash> locator = new ArrayList<Sha256Hash>(1);
locator.add(params.getGenesisBlock().getHash());
Sha256Hash hashTo = new Sha256Hash("0000000000000000000000000000000000000000000000000000000000000000");
int rulesSinceFirstFail = 0;
for (Rule rule : blockList.list) {
if (rule instanceof FullBlockTestGenerator.BlockAndValidity) {
FullBlockTestGenerator.BlockAndValidity block = (FullBlockTestGenerator.BlockAndValidity) rule;
boolean threw = false;
Block nextBlock = preloadedBlocks.get(((FullBlockTestGenerator.BlockAndValidity) rule).blockHash);
// Often load at least one block because sometimes we have duplicates with the same hash (b56/57)
for (int i = 0; i < 1
|| nextBlock == null || !nextBlock.getHash().equals(block.blockHash);
i++) {
try {
Block b = blocks.next();
Block oldBlockWithSameHash = preloadedBlocks.put(b.getHash(), b);
if (oldBlockWithSameHash != null && oldBlockWithSameHash.getTransactions().size() != b.getTransactions().size())
blocksRequested.remove(b.getHash());
nextBlock = preloadedBlocks.get(block.blockHash);
} catch (NoSuchElementException e) {
if (nextBlock == null || !nextBlock.getHash().equals(block.blockHash))
throw e;
}
}
currentBlock.block = nextBlock;
log.info("Testing block {} {}", block.ruleName, currentBlock.block.getHash());
try {
if (chain.add(nextBlock) != block.connects) {
log.error("ERROR: Block didn't match connects flag on block \"" + block.ruleName + "\"");
rulesSinceFirstFail++;
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("ERROR: Block didn't match throws flag on block \"" + block.ruleName + "\"");
e.printStackTrace();
rulesSinceFirstFail++;
} else if (block.connects) {
log.error("ERROR: Block didn't match connects flag on block \"" + block.ruleName + "\"");
e.printStackTrace();
rulesSinceFirstFail++;
}
}
if (!threw && block.throwsException) {
log.error("ERROR: Block didn't match throws flag on block \"" + block.ruleName + "\"");
rulesSinceFirstFail++;
} else if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("ERROR: New block head didn't match the correct value after block \"" + block.ruleName + "\"");
rulesSinceFirstFail++;
} else if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("ERROR: New block head didn't match the correct height after block " + block.ruleName);
rulesSinceFirstFail++;
}
// Shouldnt double-request
boolean shouldntRequest = blocksRequested.contains(nextBlock.getHash());
if (shouldntRequest)
blocksRequested.remove(nextBlock.getHash());
InventoryMessage message = new InventoryMessage(params);
message.addBlock(nextBlock);
bitcoind.sendMessage(message);
log.info("Sent inv with block " + nextBlock.getHashAsString());
if (blocksPendingSend.contains(nextBlock.getHash())) {
bitcoind.sendMessage(nextBlock);
log.info("Sent full block " + nextBlock.getHashAsString());
}
// bitcoind doesn't request blocks inline so we can't rely on a ping for synchronization
for (int i = 0; !shouldntRequest && !blocksRequested.contains(nextBlock.getHash()); i++) {
int SLEEP_TIME = 1;
if (i % 1000/SLEEP_TIME == 1000/SLEEP_TIME - 1)
log.error("bitcoind still hasn't requested block " + block.ruleName + " with hash " + nextBlock.getHash());
Thread.sleep(SLEEP_TIME);
if (i > 60000/SLEEP_TIME) {
log.error("bitcoind failed to request block " + block.ruleName);
System.exit(1);
}
}
if (shouldntRequest) {
Thread.sleep(100);
if (blocksRequested.contains(nextBlock.getHash())) {
log.error("ERROR: bitcoind re-requested block " + block.ruleName + " with hash " + nextBlock.getHash());
rulesSinceFirstFail++;
}
}
// If the block throws, we may want to get bitcoind to request the same block again
if (block.throwsException)
blocksRequested.remove(nextBlock.getHash());
//bitcoind.sendMessage(nextBlock);
locator.clear();
locator.add(bitcoindChainHead);
bitcoind.sendMessage(new GetHeadersMessage(params, locator, hashTo));
bitcoind.ping().get();
if (!chain.getChainHead().getHeader().getHash().equals(bitcoindChainHead)) {
rulesSinceFirstFail++;
log.error("ERROR: bitcoind and bitcoinj acceptance differs on block \"" + block.ruleName + "\"");
}
if (block.sendOnce)
preloadedBlocks.remove(nextBlock.getHash());
log.info("Block \"" + block.ruleName + "\" completed processing");
} else if (rule instanceof MemoryPoolState) {
MemoryPoolMessage message = new MemoryPoolMessage();
bitcoind.sendMessage(message);
bitcoind.ping().get();
if (mostRecentInv == null && !((MemoryPoolState) rule).mempool.isEmpty()) {
log.error("ERROR: bitcoind had an empty mempool, but we expected some transactions on rule " + rule.ruleName);
rulesSinceFirstFail++;
} else if (mostRecentInv != null && ((MemoryPoolState) rule).mempool.isEmpty()) {
log.error("ERROR: bitcoind had a non-empty mempool, but we expected an empty one on rule " + rule.ruleName);
rulesSinceFirstFail++;
} else if (mostRecentInv != null) {
Set<InventoryItem> originalRuleSet = new HashSet<InventoryItem>(((MemoryPoolState)rule).mempool);
boolean matches = mostRecentInv.items.size() == ((MemoryPoolState)rule).mempool.size();
for (InventoryItem item : mostRecentInv.items)
if (!((MemoryPoolState) rule).mempool.remove(item))
matches = false;
if (matches)
continue;
log.error("bitcoind's mempool didn't match what we were expecting on rule " + rule.ruleName);
log.info(" bitcoind's mempool was: ");
for (InventoryItem item : mostRecentInv.items)
log.info(" " + item.hash);
log.info(" The expected mempool was: ");
for (InventoryItem item : originalRuleSet)
log.info(" " + item.hash);
rulesSinceFirstFail++;
}
mostRecentInv = null;
} else if (rule instanceof UTXORule) {
if (bitcoind.getPeerVersionMessage().isGetUTXOsSupported()) {
UTXORule r = (UTXORule) rule;
UTXOsMessage result = bitcoind.getUTXOs(r.query).get();
if (!result.equals(r.result)) {
log.error("utxo result was not what we expected.");
log.error("Wanted {}", r.result);
log.error("but got {}", result);
rulesSinceFirstFail++;
} else {
log.info("Successful utxo query {}: {}", r.ruleName, result);
}
}
} else {
throw new RuntimeException("Unknown rule");
}
if (rulesSinceFirstFail > 0)
rulesSinceFirstFail++;
if (rulesSinceFirstFail > 6)
System.exit(1);
}
if (unexpectedInvs.get() > 0)
log.error("ERROR: Got " + unexpectedInvs.get() + " unexpected invs from bitcoind");
log.info("Done testing.");
System.exit(rulesSinceFirstFail > 0 || unexpectedInvs.get() > 0 ? 1 : 0);
}
}

View File

@ -1,458 +0,0 @@
/**
* Copyright 2011 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.core.Wallet.BalanceType;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet2Params;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.store.BlockStore;
import com.dogecoin.dogecoinj.store.MemoryBlockStore;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeBlock;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeTx;
import static org.junit.Assert.*;
// Handling of chain splits/reorgs are in ChainSplitTests.
public class BlockChainTest {
private BlockChain testNetChain;
private Wallet wallet;
private BlockChain chain;
private BlockStore blockStore;
private Address coinbaseTo;
private NetworkParameters unitTestParams;
private final StoredBlock[] block = new StoredBlock[1];
private Transaction coinbaseTransaction;
private static class TweakableTestNet2Params extends TestNet2Params {
public void setMaxTarget(BigInteger limit) {
maxTarget = limit;
}
}
private static final TweakableTestNet2Params testNet = new TweakableTestNet2Params();
private void resetBlockStore() {
blockStore = new MemoryBlockStore(unitTestParams);
}
@Before
public void setUp() throws Exception {
BriefLogFormatter.initVerbose();
testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet));
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Coin.ZERO;
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams) {
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
super.receiveFromBlock(tx, block, blockType, relativityOffset);
BlockChainTest.this.block[0] = block;
if (tx.isCoinBase()) {
BlockChainTest.this.coinbaseTransaction = tx;
}
}
};
wallet.freshReceiveKey();
resetBlockStore();
chain = new BlockChain(unitTestParams, wallet, blockStore);
coinbaseTo = wallet.currentReceiveKey().toAddress(unitTestParams);
}
@After
public void tearDown() {
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
}
@Test
public void testBasicChaining() throws Exception {
// Check that we can plug a few blocks together and the futures work.
ListenableFuture<StoredBlock> future = testNetChain.getHeightFuture(2);
// Block 1 from the testnet.
Block b1 = getBlock1();
assertTrue(testNetChain.add(b1));
assertFalse(future.isDone());
// Block 2 from the testnet.
Block b2 = getBlock2();
// Let's try adding an invalid block.
long n = b2.getNonce();
try {
b2.setNonce(12345);
testNetChain.add(b2);
fail();
} catch (VerificationException e) {
b2.setNonce(n);
}
// Now it works because we reset the nonce.
assertTrue(testNetChain.add(b2));
assertTrue(future.isDone());
assertEquals(2, future.get().getHeight());
}
@Test
public void receiveCoins() throws Exception {
// Quick check that we can actually receive coins.
Transaction tx1 = createFakeTx(unitTestParams,
COIN,
wallet.currentReceiveKey().toAddress(unitTestParams));
Block b1 = createFakeBlock(blockStore, tx1).block;
chain.add(b1);
assertTrue(wallet.getBalance().signum() > 0);
}
@Test
public void merkleRoots() throws Exception {
// Test that merkle root verification takes place when a relevant transaction is present and doesn't when
// there isn't any such tx present (as an optimization).
Transaction tx1 = createFakeTx(unitTestParams,
COIN,
wallet.currentReceiveKey().toAddress(unitTestParams));
Block b1 = createFakeBlock(blockStore, tx1).block;
chain.add(b1);
resetBlockStore();
Sha256Hash hash = b1.getMerkleRoot();
b1.setMerkleRoot(Sha256Hash.ZERO_HASH);
try {
chain.add(b1);
fail();
} catch (VerificationException e) {
// Expected.
b1.setMerkleRoot(hash);
}
// Now add a second block with no relevant transactions and then break it.
Transaction tx2 = createFakeTx(unitTestParams, COIN,
new ECKey().toAddress(unitTestParams));
Block b2 = createFakeBlock(blockStore, tx2).block;
b2.getMerkleRoot();
b2.setMerkleRoot(Sha256Hash.ZERO_HASH);
b2.solve();
chain.add(b2); // Broken block is accepted because its contents don't matter to us.
}
@Test
public void unconnectedBlocks() throws Exception {
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinbaseTo);
Block b2 = b1.createNextBlock(coinbaseTo);
Block b3 = b2.createNextBlock(coinbaseTo);
// Connected.
assertTrue(chain.add(b1));
// Unconnected but stored. The head of the chain is still b1.
assertFalse(chain.add(b3));
assertEquals(chain.getChainHead().getHeader(), b1.cloneAsHeader());
// Add in the middle block.
assertTrue(chain.add(b2));
assertEquals(chain.getChainHead().getHeader(), b3.cloneAsHeader());
}
@Test
public void difficultyTransitions() throws Exception {
// Add a bunch of blocks in a loop until we reach a difficulty transition point. The unit test params have an
// artificially shortened period.
Block prev = unitTestParams.getGenesisBlock();
Utils.setMockClock(System.currentTimeMillis()/1000);
for (int i = 0; i < unitTestParams.getInterval() - 1; i++) {
Block newBlock = prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds());
assertTrue(chain.add(newBlock));
prev = newBlock;
// The fake chain should seem to be "fast" for the purposes of difficulty calculations.
Utils.rollMockClock(2);
}
// Now add another block that has no difficulty adjustment, it should be rejected.
try {
chain.add(prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds()));
fail();
} catch (VerificationException e) {
}
// Create a new block with the right difficulty target given our blistering speed relative to the huge amount
// of time it's supposed to take (set in the unit test network parameters).
Block b = prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds());
b.setDifficultyTarget(0x201fFFFFL);
b.solve();
assertTrue(chain.add(b));
// Successfully traversed a difficulty transition period.
}
@Test
public void badDifficulty() throws Exception {
assertTrue(testNetChain.add(getBlock1()));
Block b2 = getBlock2();
assertTrue(testNetChain.add(b2));
Block bad = new Block(testNet);
// Merkle root can be anything here, doesn't matter.
bad.setMerkleRoot(new Sha256Hash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
// Nonce was just some number that made the hash < difficulty limit set below, it can be anything.
bad.setNonce(140548933);
bad.setTime(1279242649);
bad.setPrevBlockHash(b2.getHash());
// We're going to make this block so easy 50% of solutions will pass, and check it gets rejected for having a
// bad difficulty target. Unfortunately the encoding mechanism means we cannot make one that accepts all
// solutions.
bad.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
try {
testNetChain.add(bad);
// The difficulty target above should be rejected on the grounds of being easier than the networks
// allowable difficulty.
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage(), e.getCause().getMessage().contains("Difficulty target is bad"));
}
// Accept any level of difficulty now.
BigInteger oldVal = testNet.getMaxTarget();
testNet.setMaxTarget(new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16));
try {
testNetChain.add(bad);
// We should not get here as the difficulty target should not be changing at this point.
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage(), e.getCause().getMessage().contains("Unexpected change in difficulty"));
}
testNet.setMaxTarget(oldVal);
// TODO: Test difficulty change is not out of range when a transition period becomes valid.
}
@Test
public void duplicates() throws Exception {
// Adding a block twice should not have any effect, in particular it should not send the block to the wallet.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinbaseTo);
Block b2 = b1.createNextBlock(coinbaseTo);
Block b3 = b2.createNextBlock(coinbaseTo);
assertTrue(chain.add(b1));
assertEquals(b1, block[0].getHeader());
assertTrue(chain.add(b2));
assertEquals(b2, block[0].getHeader());
assertTrue(chain.add(b3));
assertEquals(b3, block[0].getHeader());
assertEquals(b3, chain.getChainHead().getHeader());
assertTrue(chain.add(b2));
assertEquals(b3, chain.getChainHead().getHeader());
// Wallet was NOT called with the new block because the duplicate add was spotted.
assertEquals(b3, block[0].getHeader());
}
@Test
public void intraBlockDependencies() throws Exception {
// Covers issue 166 in which transactions that depend on each other inside a block were not always being
// considered relevant.
Address somebodyElse = new ECKey().toAddress(unitTestParams);
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(somebodyElse);
ECKey key = wallet.freshReceiveKey();
Address addr = key.toAddress(unitTestParams);
// Create a tx that gives us some coins, and another that spends it to someone else in the same block.
Transaction t1 = FakeTxBuilder.createFakeTx(unitTestParams, COIN, addr);
Transaction t2 = new Transaction(unitTestParams);
t2.addInput(t1.getOutputs().get(0));
t2.addOutput(valueOf(2, 0), somebodyElse);
b1.addTransaction(t1);
b1.addTransaction(t2);
b1.solve();
chain.add(b1);
assertEquals(Coin.ZERO, wallet.getBalance());
}
@Test
public void coinbaseTransactionAvailability() throws Exception {
// Check that a coinbase transaction is only available to spend after NetworkParameters.getSpendableCoinbaseDepth() blocks.
// Create a second wallet to receive the coinbase spend.
Wallet wallet2 = new Wallet(unitTestParams);
ECKey receiveKey = wallet2.freshReceiveKey();
chain.addWallet(wallet2);
Address addressToSendTo = receiveKey.toAddress(unitTestParams);
// Create a block, sending the coinbase to the coinbaseTo address (which is in the wallet).
Block b1 = unitTestParams.getGenesisBlock().createNextBlockWithCoinbase(wallet.currentReceiveKey().getPubKey());
chain.add(b1);
// Check a transaction has been received.
assertNotNull(coinbaseTransaction);
// The coinbase tx is not yet available to spend.
assertEquals(Coin.ZERO, wallet.getBalance());
assertEquals(wallet.getBalance(BalanceType.ESTIMATED), FIFTY_COINS);
assertTrue(!coinbaseTransaction.isMature());
// Attempt to spend the coinbase - this should fail as the coinbase is not mature yet.
try {
wallet.createSend(addressToSendTo, valueOf(49, 0));
fail();
} catch (InsufficientMoneyException e) {
}
// Check that the coinbase is unavailable to spend for the next spendableCoinbaseDepth - 2 blocks.
for (int i = 0; i < unitTestParams.getSpendableCoinbaseDepth() - 2; i++) {
// Non relevant tx - just for fake block creation.
Transaction tx2 = createFakeTx(unitTestParams, COIN,
new ECKey().toAddress(unitTestParams));
Block b2 = createFakeBlock(blockStore, tx2).block;
chain.add(b2);
// Wallet still does not have the coinbase transaction available for spend.
assertEquals(Coin.ZERO, wallet.getBalance());
assertEquals(wallet.getBalance(BalanceType.ESTIMATED), FIFTY_COINS);
// The coinbase transaction is still not mature.
assertTrue(!coinbaseTransaction.isMature());
// Attempt to spend the coinbase - this should fail.
try {
wallet.createSend(addressToSendTo, valueOf(49, 0));
fail();
} catch (InsufficientMoneyException e) {
}
}
// Give it one more block - should now be able to spend coinbase transaction. Non relevant tx.
Transaction tx3 = createFakeTx(unitTestParams, COIN, new ECKey().toAddress(unitTestParams));
Block b3 = createFakeBlock(blockStore, tx3).block;
chain.add(b3);
// Wallet now has the coinbase transaction available for spend.
assertEquals(wallet.getBalance(), FIFTY_COINS);
assertEquals(wallet.getBalance(BalanceType.ESTIMATED), FIFTY_COINS);
assertTrue(coinbaseTransaction.isMature());
// Create a spend with the coinbase BTC to the address in the second wallet - this should now succeed.
Transaction coinbaseSend2 = wallet.createSend(addressToSendTo, valueOf(49, 0));
assertNotNull(coinbaseSend2);
// Commit the coinbaseSpend to the first wallet and check the balances decrement.
wallet.commitTx(coinbaseSend2);
assertEquals(wallet.getBalance(BalanceType.ESTIMATED), COIN);
// Available balance is zero as change has not been received from a block yet.
assertEquals(wallet.getBalance(BalanceType.AVAILABLE), ZERO);
// Give it one more block - change from coinbaseSpend should now be available in the first wallet.
Block b4 = createFakeBlock(blockStore, coinbaseSend2).block;
chain.add(b4);
assertEquals(wallet.getBalance(BalanceType.AVAILABLE), COIN);
// Check the balances in the second wallet.
assertEquals(wallet2.getBalance(BalanceType.ESTIMATED), valueOf(49, 0));
assertEquals(wallet2.getBalance(BalanceType.AVAILABLE), valueOf(49, 0));
}
// Some blocks from the test net.
private static Block getBlock2() throws Exception {
Block b2 = new Block(testNet);
b2.setMerkleRoot(new Sha256Hash("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
b2.setNonce(2642058077L);
b2.setTime(1296734343L);
b2.setPrevBlockHash(new Sha256Hash("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604"));
assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString());
b2.verifyHeader();
return b2;
}
private static Block getBlock1() throws Exception {
Block b1 = new Block(testNet);
b1.setMerkleRoot(new Sha256Hash("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
b1.setNonce(236038445);
b1.setTime(1296734340);
b1.setPrevBlockHash(new Sha256Hash("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"));
assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString());
b1.verifyHeader();
return b1;
}
@Test
public void estimatedBlockTime() throws Exception {
NetworkParameters params = MainNetParams.get();
BlockChain prod = new BlockChain(params, new MemoryBlockStore(params));
Date d = prod.estimateBlockTime(200000);
// The actual date of block 200,000 was 2012-09-22 10:47:00
assertEquals(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US).parse("2012-10-23T08:35:05.000-0700"), d);
}
@Test
public void falsePositives() throws Exception {
double decay = AbstractBlockChain.FP_ESTIMATOR_ALPHA;
assertTrue(0 == chain.getFalsePositiveRate()); // Exactly
chain.trackFalsePositives(55);
assertEquals(decay * 55, chain.getFalsePositiveRate(), 1e-4);
chain.trackFilteredTransactions(550);
double rate1 = chain.getFalsePositiveRate();
// Run this scenario a few more time for the filter to converge
for (int i = 1 ; i < 10 ; i++) {
chain.trackFalsePositives(55);
chain.trackFilteredTransactions(550);
}
// Ensure we are within 10%
assertEquals(0.1, chain.getFalsePositiveRate(), 0.01);
// Check that we get repeatable results after a reset
chain.resetFalsePositiveEstimate();
assertTrue(0 == chain.getFalsePositiveRate()); // Exactly
chain.trackFalsePositives(55);
assertEquals(decay * 55, chain.getFalsePositiveRate(), 1e-4);
chain.trackFilteredTransactions(550);
assertEquals(rate1, chain.getFalsePositiveRate(), 1e-4);
}
@Test
public void rollbackBlockStore() throws Exception {
// This test simulates an issue on Android, that causes the VM to crash while receiving a block, so that the
// block store is persisted but the wallet is not.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinbaseTo);
Block b2 = b1.createNextBlock(coinbaseTo);
// Add block 1, no frills.
assertTrue(chain.add(b1));
assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader());
assertEquals(1, chain.getBestChainHeight());
assertEquals(1, wallet.getLastBlockSeenHeight());
// Add block 2 while wallet is disconnected, to simulate crash.
chain.removeWallet(wallet);
assertTrue(chain.add(b2));
assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader());
assertEquals(2, chain.getBestChainHeight());
assertEquals(1, wallet.getLastBlockSeenHeight());
// Add wallet back. This will detect the height mismatch and repair the damage done.
chain.addWallet(wallet);
assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader());
assertEquals(1, chain.getBestChainHeight());
assertEquals(1, wallet.getLastBlockSeenHeight());
// Now add block 2 correctly.
assertTrue(chain.add(b2));
assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader());
assertEquals(2, chain.getBestChainHeight());
assertEquals(2, wallet.getLastBlockSeenHeight());
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,91 +0,0 @@
/*
* Copyright 2012 Matt Corallo
* 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.wallet.KeyChainGroup;
import org.junit.Test;
import java.util.Arrays;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.*;
public class BloomFilterTest {
@Test
public void insertSerializeTest() {
BloomFilter filter = new BloomFilter(3, 0.01, 0, BloomFilter.BloomUpdate.UPDATE_ALL);
filter.insert(HEX.decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8"));
assertTrue (filter.contains(HEX.decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8")));
// One bit different in first byte
assertFalse(filter.contains(HEX.decode("19108ad8ed9bb6274d3980bab5a85c048f0950c8")));
filter.insert(HEX.decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee"));
assertTrue(filter.contains(HEX.decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
filter.insert(HEX.decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5"));
assertTrue(filter.contains(HEX.decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5")));
// Value generated by the reference client
assertTrue(Arrays.equals(HEX.decode("03614e9b050000000000000001"), filter.bitcoinSerialize()));
}
@Test
public void insertSerializeTestWithTweak() {
BloomFilter filter = new BloomFilter(3, 0.01, 2147483649L);
filter.insert(HEX.decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8"));
assertTrue (filter.contains(HEX.decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8")));
// One bit different in first byte
assertFalse(filter.contains(HEX.decode("19108ad8ed9bb6274d3980bab5a85c048f0950c8")));
filter.insert(HEX.decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee"));
assertTrue(filter.contains(HEX.decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
filter.insert(HEX.decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5"));
assertTrue(filter.contains(HEX.decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5")));
// Value generated by the reference client
assertTrue(Arrays.equals(HEX.decode("03ce4299050000000100008002"), filter.bitcoinSerialize()));
}
@Test
public void walletTest() throws Exception {
NetworkParameters params = MainNetParams.get();
DumpedPrivateKey privKey = new DumpedPrivateKey(params, "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C");
Address addr = privKey.getKey().toAddress(params);
assertTrue(addr.toString().equals("17Wx1GQfyPTNWpQMHrTwRSMTCAonSiZx9e"));
KeyChainGroup group = new KeyChainGroup(params);
// Add a random key which happens to have been used in a recent generation
group.importKeys(privKey.getKey(), ECKey.fromPublicOnly(HEX.decode("03cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99")));
Wallet wallet = new Wallet(params, group);
wallet.commitTx(new Transaction(params, HEX.decode("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d038754030114062f503253482fffffffff01c05e559500000000232103cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99ac00000000")));
// We should have 2 per pubkey, and one for the pay-2-pubkey output we have
assertEquals(5, wallet.getBloomFilterElementCount());
BloomFilter filter = wallet.getBloomFilter(wallet.getBloomFilterElementCount(), 0.001, 0);
// Value generated by the reference client
assertTrue(Arrays.equals(HEX.decode("082ae5edc8e51d4a03080000000000000002"), filter.bitcoinSerialize()));
}
}

View File

@ -1,688 +0,0 @@
/*
* Copyright 2012 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.core.TransactionConfidence.ConfidenceType;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.store.MemoryBlockStore;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.utils.Threading;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class ChainSplitTest {
private static final Logger log = LoggerFactory.getLogger(ChainSplitTest.class);
private NetworkParameters unitTestParams;
private Wallet wallet;
private BlockChain chain;
private Address coinsTo;
private Address coinsTo2;
private Address someOtherGuy;
private MemoryBlockStore blockStore;
@Before
public void setUp() throws Exception {
BriefLogFormatter.init();
Utils.setMockClock(); // Use mock clock
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Coin.ZERO;
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams);
ECKey key1 = wallet.freshReceiveKey();
ECKey key2 = wallet.freshReceiveKey();
blockStore = new MemoryBlockStore(unitTestParams);
chain = new BlockChain(unitTestParams, wallet, blockStore);
coinsTo = key1.toAddress(unitTestParams);
coinsTo2 = key2.toAddress(unitTestParams);
someOtherGuy = new ECKey().toAddress(unitTestParams);
}
@Test
public void testForking1() throws Exception {
// Check that if the block chain forks, we end up using the right chain. Only tests inbound transactions
// (receiving coins). Checking that we understand reversed spends is in testForking2.
final AtomicBoolean reorgHappened = new AtomicBoolean();
final AtomicInteger walletChanged = new AtomicInteger();
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onReorganize(Wallet wallet) {
reorgHappened.set(true);
}
@Override
public void onWalletChanged(Wallet wallet) {
walletChanged.incrementAndGet();
}
});
// Start by building a couple of blocks on top of the genesis block.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
Block b2 = b1.createNextBlock(coinsTo);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
Threading.waitForUserCode();
assertFalse(reorgHappened.get());
assertEquals(2, walletChanged.get());
// We got two blocks which sent 50 coins each to us.
assertEquals(Coin.valueOf(100, 0), wallet.getBalance());
// We now have the following chain:
// genesis -> b1 -> b2
//
// so fork like this:
//
// genesis -> b1 -> b2
// \-> b3
//
// Nothing should happen at this point. We saw b2 first so it takes priority.
Block b3 = b1.createNextBlock(someOtherGuy);
assertTrue(chain.add(b3));
Threading.waitForUserCode();
assertFalse(reorgHappened.get()); // No re-org took place.
assertEquals(2, walletChanged.get());
assertEquals(Coin.valueOf(100, 0), wallet.getBalance());
// Check we can handle multi-way splits: this is almost certainly going to be extremely rare, but we have to
// handle it anyway. The same transaction appears in b7/b8 (side chain) but not b2 or b3.
// genesis -> b1--> b2
// |-> b3
// |-> b7 (x)
// \-> b8 (x)
Block b7 = b1.createNextBlock(coinsTo);
assertTrue(chain.add(b7));
Block b8 = b1.createNextBlock(coinsTo);
final Transaction t = b7.getTransactions().get(1);
final Sha256Hash tHash = t.getHash();
b8.addTransaction(t);
b8.solve();
assertTrue(chain.add(roundtrip(b8)));
Threading.waitForUserCode();
assertEquals(2, wallet.getTransaction(tHash).getAppearsInHashes().size());
assertFalse(reorgHappened.get()); // No re-org took place.
assertEquals(5, walletChanged.get());
assertEquals(Coin.valueOf(100, 0), wallet.getBalance());
// Now we add another block to make the alternative chain longer.
assertTrue(chain.add(b3.createNextBlock(someOtherGuy)));
Threading.waitForUserCode();
assertTrue(reorgHappened.get()); // Re-org took place.
assertEquals(6, walletChanged.get());
reorgHappened.set(false);
//
// genesis -> b1 -> b2
// \-> b3 -> b4
// We lost some coins! b2 is no longer a part of the best chain so our available balance should drop to 50.
// It's now pending reconfirmation.
assertEquals(FIFTY_COINS, wallet.getBalance());
// ... and back to the first chain.
Block b5 = b2.createNextBlock(coinsTo);
Block b6 = b5.createNextBlock(coinsTo);
assertTrue(chain.add(b5));
assertTrue(chain.add(b6));
//
// genesis -> b1 -> b2 -> b5 -> b6
// \-> b3 -> b4
//
Threading.waitForUserCode();
assertTrue(reorgHappened.get());
assertEquals(9, walletChanged.get());
assertEquals(Coin.valueOf(200, 0), wallet.getBalance());
}
@Test
public void testForking2() throws Exception {
// Check that if the chain forks and new coins are received in the alternate chain our balance goes up
// after the re-org takes place.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(someOtherGuy);
Block b2 = b1.createNextBlock(someOtherGuy);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
// genesis -> b1 -> b2
// \-> b3 -> b4
assertEquals(Coin.ZERO, wallet.getBalance());
Block b3 = b1.createNextBlock(coinsTo);
Block b4 = b3.createNextBlock(someOtherGuy);
assertTrue(chain.add(b3));
assertEquals(Coin.ZERO, wallet.getBalance());
assertTrue(chain.add(b4));
assertEquals(FIFTY_COINS, wallet.getBalance());
}
@Test
public void testForking3() throws Exception {
// Check that we can handle our own spends being rolled back by a fork.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
assertEquals(FIFTY_COINS, wallet.getBalance());
Address dest = new ECKey().toAddress(unitTestParams);
Transaction spend = wallet.createSend(dest, valueOf(10, 0));
wallet.commitTx(spend);
// Waiting for confirmation ... make it eligible for selection.
assertEquals(Coin.ZERO, wallet.getBalance());
spend.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1, 2, 3, 4})));
spend.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{5,6,7,8})));
assertEquals(ConfidenceType.PENDING, spend.getConfidence().getConfidenceType());
assertEquals(valueOf(40, 0), wallet.getBalance());
Block b2 = b1.createNextBlock(someOtherGuy);
b2.addTransaction(spend);
b2.solve();
chain.add(roundtrip(b2));
// We have 40 coins in change.
assertEquals(ConfidenceType.BUILDING, spend.getConfidence().getConfidenceType());
// genesis -> b1 (receive coins) -> b2 (spend coins)
// \-> b3 -> b4
Block b3 = b1.createNextBlock(someOtherGuy);
Block b4 = b3.createNextBlock(someOtherGuy);
chain.add(b3);
chain.add(b4);
// b4 causes a re-org that should make our spend go pending again.
assertEquals(valueOf(40, 0), wallet.getBalance());
assertEquals(ConfidenceType.PENDING, spend.getConfidence().getConfidenceType());
}
@Test
public void testForking4() throws Exception {
// Check that we can handle external spends on an inactive chain becoming active. An external spend is where
// we see a transaction that spends our own coins but we did not broadcast it ourselves. This happens when
// keys are being shared between wallets.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
assertEquals(FIFTY_COINS, wallet.getBalance());
Address dest = new ECKey().toAddress(unitTestParams);
Transaction spend = wallet.createSend(dest, FIFTY_COINS);
// We do NOT confirm the spend here. That means it's not considered to be pending because createSend is
// stateless. For our purposes it is as if some other program with our keys created the tx.
//
// genesis -> b1 (receive 50) --> b2
// \-> b3 (external spend) -> b4
Block b2 = b1.createNextBlock(someOtherGuy);
chain.add(b2);
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(spend);
b3.solve();
chain.add(roundtrip(b3));
// The external spend is now pending.
assertEquals(ZERO, wallet.getBalance());
Transaction tx = wallet.getTransaction(spend.getHash());
assertEquals(ConfidenceType.PENDING, tx.getConfidence().getConfidenceType());
Block b4 = b3.createNextBlock(someOtherGuy);
chain.add(b4);
// The external spend is now active.
assertEquals(ZERO, wallet.getBalance());
assertEquals(ConfidenceType.BUILDING, tx.getConfidence().getConfidenceType());
}
@Test
public void testForking5() throws Exception {
// Test the standard case in which a block containing identical transactions appears on a side chain.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
final Transaction t = b1.transactions.get(1);
assertEquals(FIFTY_COINS, wallet.getBalance());
// genesis -> b1
// -> b2
Block b2 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
Transaction b2coinbase = b2.transactions.get(0);
b2.transactions.clear();
b2.addTransaction(b2coinbase);
b2.addTransaction(t);
b2.solve();
chain.add(roundtrip(b2));
assertEquals(FIFTY_COINS, wallet.getBalance());
assertTrue(wallet.isConsistent());
assertEquals(2, wallet.getTransaction(t.getHash()).getAppearsInHashes().size());
// -> b2 -> b3
Block b3 = b2.createNextBlock(someOtherGuy);
chain.add(b3);
assertEquals(FIFTY_COINS, wallet.getBalance());
}
private Block roundtrip(Block b2) throws ProtocolException {
return new Block(unitTestParams, b2.bitcoinSerialize());
}
@Test
public void testForking6() throws Exception {
// Test the case in which a side chain block contains a tx, and then it appears in the main chain too.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(someOtherGuy);
chain.add(b1);
// genesis -> b1
// -> b2
Block b2 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b2);
assertEquals(Coin.ZERO, wallet.getBalance());
// genesis -> b1 -> b3
// -> b2
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(b2.transactions.get(1));
b3.solve();
chain.add(roundtrip(b3));
assertEquals(FIFTY_COINS, wallet.getBalance());
}
@Test
public void testDoubleSpendOnFork() throws Exception {
// Check what happens when a re-org happens and one of our confirmed transactions becomes invalidated by a
// double spend on the new best chain.
final boolean[] eventCalled = new boolean[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.DEAD)
eventCalled[0] = true;
}
});
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
Transaction t1 = wallet.createSend(someOtherGuy, valueOf(10, 0));
Address yetAnotherGuy = new ECKey().toAddress(unitTestParams);
Transaction t2 = wallet.createSend(yetAnotherGuy, valueOf(20, 0));
wallet.commitTx(t1);
// Receive t1 as confirmed by the network.
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b2.addTransaction(t1);
b2.solve();
chain.add(roundtrip(b2));
// Now we make a double spend become active after a re-org.
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
// Should have seen a double spend.
assertTrue(eventCalled[0]);
assertEquals(valueOf(30, 0), wallet.getBalance());
}
@Test
public void testDoubleSpendOnForkPending() throws Exception {
// Check what happens when a re-org happens and one of our unconfirmed transactions becomes invalidated by a
// double spend on the new best chain.
final Transaction[] eventDead = new Transaction[1];
final Transaction[] eventReplacement = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() ==
TransactionConfidence.ConfidenceType.DEAD) {
eventDead[0] = tx;
eventReplacement[0] = tx.getConfidence().getOverridingTransaction();
}
}
});
// Start with 50 coins.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
Transaction t1 = checkNotNull(wallet.createSend(someOtherGuy, valueOf(10, 0)));
Address yetAnotherGuy = new ECKey().toAddress(unitTestParams);
Transaction t2 = checkNotNull(wallet.createSend(yetAnotherGuy, valueOf(20, 0)));
wallet.commitTx(t1);
// t1 is still pending ...
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b2);
assertEquals(ZERO, wallet.getBalance());
assertEquals(valueOf(40, 0), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
// Now we make a double spend become active after a re-org.
// genesis -> b1 -> b2 [t1 pending]
// \-> b3 (t2) -> b4
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
// Should have seen a double spend against the pending pool.
// genesis -> b1 -> b2 [t1 dead and exited the miners mempools]
// \-> b3 (t2) -> b4
assertEquals(t1, eventDead[0]);
assertEquals(t2, eventReplacement[0]);
assertEquals(valueOf(30, 0), wallet.getBalance());
// ... and back to our own parallel universe.
Block b5 = b2.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b5);
Block b6 = b5.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b6);
// genesis -> b1 -> b2 -> b5 -> b6 [t1 still dead]
// \-> b3 [t2 resurrected and now pending] -> b4
assertEquals(ZERO, wallet.getBalance());
// t2 is pending - resurrected double spends take precedence over our dead transactions (which are in nobodies
// mempool by this point).
t1 = checkNotNull(wallet.getTransaction(t1.getHash()));
t2 = checkNotNull(wallet.getTransaction(t2.getHash()));
assertEquals(ConfidenceType.DEAD, t1.getConfidence().getConfidenceType());
assertEquals(ConfidenceType.PENDING, t2.getConfidence().getConfidenceType());
}
@Test
public void txConfidenceLevels() throws Exception {
// Check that as the chain forks and re-orgs, the confidence data associated with each transaction is
// maintained correctly.
final ArrayList<Transaction> txns = new ArrayList<Transaction>(3);
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
txns.add(tx);
}
});
// Start by building three blocks on top of the genesis block. All send to us.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
BigInteger work1 = b1.getWork();
Block b2 = b1.createNextBlock(coinsTo2);
BigInteger work2 = b2.getWork();
Block b3 = b2.createNextBlock(coinsTo2);
BigInteger work3 = b3.getWork();
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
assertTrue(chain.add(b3));
Threading.waitForUserCode();
// Check the transaction confidence levels are correct.
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
// We now have the following chain:
// genesis -> b1 -> b2 -> b3
//
// so fork like this:
//
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5
//
// Nothing should happen at this point. We saw b2 and b3 first so it takes priority.
Block b4 = b1.createNextBlock(someOtherGuy);
BigInteger work4 = b4.getWork();
Block b5 = b4.createNextBlock(someOtherGuy);
BigInteger work5 = b5.getWork();
assertTrue(chain.add(b4));
assertTrue(chain.add(b5));
Threading.waitForUserCode();
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
// Now we add another block to make the alternative chain longer.
Block b6 = b5.createNextBlock(someOtherGuy);
BigInteger work6 = b6.getWork();
assertTrue(chain.add(b6));
//
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5 -> b6
//
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(4, txns.get(0).getConfidence().getDepthInBlocks());
// Transaction 1 (in block b2) is now on a side chain, so it goes pending (not see in chain).
assertEquals(ConfidenceType.PENDING, txns.get(1).getConfidence().getConfidenceType());
try {
txns.get(1).getConfidence().getAppearedAtChainHeight();
fail();
} catch (IllegalStateException e) {}
assertEquals(0, txns.get(1).getConfidence().getDepthInBlocks());
// ... and back to the first chain.
Block b7 = b3.createNextBlock(coinsTo);
BigInteger work7 = b7.getWork();
Block b8 = b7.createNextBlock(coinsTo);
BigInteger work8 = b7.getWork();
assertTrue(chain.add(b7));
assertTrue(chain.add(b8));
//
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
// \-> b4 -> b5 -> b6
//
// This should be enabled, once we figure out the best way to inform the user of how the wallet is changing
// during the re-org.
//assertEquals(5, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(5, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(4, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(3, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(Coin.valueOf(250, 0), wallet.getBalance());
// Now add two more blocks that don't send coins to us. Despite being irrelevant the wallet should still update.
Block b9 = b8.createNextBlock(someOtherGuy);
Block b10 = b9.createNextBlock(someOtherGuy);
chain.add(b9);
chain.add(b10);
BigInteger extraWork = b9.getWork().add(b10.getWork());
assertEquals(7, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(6, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(5, txns.get(2).getConfidence().getDepthInBlocks());
}
@Test
public void orderingInsideBlock() throws Exception {
// Test that transactions received in the same block have their ordering preserved when reorganising.
// This covers issue 468.
// Receive some money to the wallet.
Transaction t1 = FakeTxBuilder.createFakeTx(unitTestParams, COIN, coinsTo);
final Block b1 = FakeTxBuilder.makeSolvedTestBlock(unitTestParams.genesisBlock, t1);
chain.add(b1);
// Send a couple of payments one after the other (so the second depends on the change output of the first).
wallet.allowSpendingUnconfirmedTransactions();
Transaction t2 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), CENT));
wallet.commitTx(t2);
Transaction t3 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), CENT));
wallet.commitTx(t3);
chain.add(FakeTxBuilder.makeSolvedTestBlock(b1, t2, t3));
final Coin coins0point98 = COIN.subtract(CENT).subtract(CENT);
assertEquals(coins0point98, wallet.getBalance());
// Now round trip the wallet and force a re-org.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
wallet.saveToFileStream(bos);
wallet = Wallet.loadFromFileStream(new ByteArrayInputStream(bos.toByteArray()));
final Block b2 = FakeTxBuilder.makeSolvedTestBlock(b1, t2, t3);
final Block b3 = FakeTxBuilder.makeSolvedTestBlock(b2);
chain.add(b2);
chain.add(b3);
// And verify that the balance is as expected. Because signatures are currently non-deterministic if the order
// isn't being stored correctly this should fail 50% of the time.
assertEquals(coins0point98, wallet.getBalance());
}
@Test
public void coinbaseDeath() throws Exception {
// Check that a coinbase tx is marked as dead after a reorg rather than pending as normal non-double-spent
// transactions would be. Also check that a dead coinbase on a sidechain is resurrected if the sidechain
// becomes the best chain once more. Finally, check that dependent transactions are killed recursively.
final ArrayList<Transaction> txns = new ArrayList<Transaction>(3);
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
txns.add(tx);
}
}, Threading.SAME_THREAD);
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(someOtherGuy);
final ECKey coinsTo2 = wallet.freshReceiveKey();
Block b2 = b1.createNextBlockWithCoinbase(coinsTo2.getPubKey());
Block b3 = b2.createNextBlock(someOtherGuy);
log.debug("Adding block b1");
assertTrue(chain.add(b1));
log.debug("Adding block b2");
assertTrue(chain.add(b2));
log.debug("Adding block b3");
assertTrue(chain.add(b3));
// We now have the following chain:
// genesis -> b1 -> b2 -> b3
//
// Check we have seen the coinbase.
assertEquals(1, txns.size());
// Check the coinbase transaction is building and in the unspent pool only.
final Transaction coinbase = txns.get(0);
assertEquals(ConfidenceType.BUILDING, coinbase.getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(coinbase.getHash()));
assertTrue(wallet.unspent.containsKey(coinbase.getHash()));
assertTrue(!wallet.spent.containsKey(coinbase.getHash()));
assertTrue(!wallet.dead.containsKey(coinbase.getHash()));
// Add blocks to b3 until we can spend the coinbase.
Block firstTip = b3;
for (int i = 0; i < unitTestParams.getSpendableCoinbaseDepth() - 2; i++) {
firstTip = firstTip.createNextBlock(someOtherGuy);
chain.add(firstTip);
}
// ... and spend.
Transaction fodder = wallet.createSend(new ECKey().toAddress(unitTestParams), FIFTY_COINS);
wallet.commitTx(fodder);
final AtomicBoolean fodderIsDead = new AtomicBoolean(false);
fodder.getConfidence().addEventListener(new TransactionConfidence.Listener() {
@Override
public void onConfidenceChanged(TransactionConfidence confidence, ChangeReason reason) {
fodderIsDead.set(confidence.getConfidenceType() == ConfidenceType.DEAD);
}
}, Threading.SAME_THREAD);
// Fork like this:
//
// genesis -> b1 -> b2 -> b3 -> [...]
// \-> b4 -> b5 -> b6 -> [...]
//
// The b4/ b5/ b6 is now the best chain
Block b4 = b1.createNextBlock(someOtherGuy);
Block b5 = b4.createNextBlock(someOtherGuy);
Block b6 = b5.createNextBlock(someOtherGuy);
log.debug("Adding block b4");
assertTrue(chain.add(b4));
log.debug("Adding block b5");
assertTrue(chain.add(b5));
log.debug("Adding block b6");
assertTrue(chain.add(b6));
Block secondTip = b6;
for (int i = 0; i < unitTestParams.getSpendableCoinbaseDepth() - 2; i++) {
secondTip = secondTip.createNextBlock(someOtherGuy);
chain.add(secondTip);
}
// Transaction 1 (in block b2) is now on a side chain and should have confidence type of dead and be in
// the dead pool only.
assertEquals(TransactionConfidence.ConfidenceType.DEAD, coinbase.getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(coinbase.getHash()));
assertTrue(!wallet.unspent.containsKey(coinbase.getHash()));
assertTrue(!wallet.spent.containsKey(coinbase.getHash()));
assertTrue(wallet.dead.containsKey(coinbase.getHash()));
assertTrue(fodderIsDead.get());
// ... and back to the first chain.
Block b7 = firstTip.createNextBlock(someOtherGuy);
Block b8 = b7.createNextBlock(someOtherGuy);
log.debug("Adding block b7");
assertTrue(chain.add(b7));
log.debug("Adding block b8");
assertTrue(chain.add(b8));
//
// genesis -> b1 -> b2 -> b3 -> [...] -> b7 -> b8
// \-> b4 -> b5 -> b6 -> [...]
//
// The coinbase transaction should now have confidence type of building once more and in the unspent pool only.
assertEquals(TransactionConfidence.ConfidenceType.BUILDING, coinbase.getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(coinbase.getHash()));
assertTrue(wallet.unspent.containsKey(coinbase.getHash()));
assertTrue(!wallet.spent.containsKey(coinbase.getHash()));
assertTrue(!wallet.dead.containsKey(coinbase.getHash()));
// However, fodder is still dead. Bitcoin Core doesn't keep killed transactions around in case they become
// valid again later. They are just deleted from the mempool for good.
// ... make the side chain dominant again.
Block b9 = secondTip.createNextBlock(someOtherGuy);
Block b10 = b9.createNextBlock(someOtherGuy);
log.debug("Adding block b9");
assertTrue(chain.add(b9));
log.debug("Adding block b10");
assertTrue(chain.add(b10));
//
// genesis -> b1 -> b2 -> b3 -> [...] -> b7 -> b8
// \-> b4 -> b5 -> b6 -> [...] -> b9 -> b10
//
// The coinbase transaction should now have the confidence type of dead and be in the dead pool only.
assertEquals(TransactionConfidence.ConfidenceType.DEAD, coinbase.getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(coinbase.getHash()));
assertTrue(!wallet.unspent.containsKey(coinbase.getHash()));
assertTrue(!wallet.spent.containsKey(coinbase.getHash()));
assertTrue(wallet.dead.containsKey(coinbase.getHash()));
}
}

View File

@ -1,140 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.core;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.core.NetworkParameters.MAX_MONEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import org.junit.Assert;
import org.junit.Test;
public class CoinTest {
@Test
public void testParseCoin() {
// String version
assertEquals(CENT, parseCoin("0.01"));
assertEquals(CENT, parseCoin("1E-2"));
assertEquals(COIN.add(CENT), parseCoin("1.01"));
assertEquals(COIN.negate(), parseCoin("-1"));
try {
parseCoin("2E-20");
org.junit.Assert.fail("should not have accepted fractional satoshis");
} catch (IllegalArgumentException expected) {
} catch (Exception e) {
org.junit.Assert.fail("should throw IllegalArgumentException");
}
}
@Test
public void testValueOf() {
// int version
assertEquals(CENT, valueOf(0, 1));
assertEquals(SATOSHI, valueOf(1));
assertEquals(NEGATIVE_SATOSHI, valueOf(-1));
assertEquals(MAX_MONEY, valueOf(MAX_MONEY.value));
assertEquals(MAX_MONEY.negate(), valueOf(MAX_MONEY.value * -1));
try {
valueOf(MAX_MONEY.value + 1);
org.junit.Assert.fail("should not have accepted too-great a monetary value");
} catch (IllegalArgumentException e) {
}
try {
valueOf( (MAX_MONEY.value * -1) - 1);
org.junit.Assert.fail("should not have accepted too-little a monetary value");
} catch (IllegalArgumentException e) {
}
try {
valueOf(Long.MIN_VALUE);
fail();
} catch (IllegalArgumentException e) {}
try {
valueOf(1, -1);
fail();
} catch (IllegalArgumentException e) {}
try {
valueOf(-1, 0);
fail();
} catch (IllegalArgumentException e) {}
}
@Test
public void testOperators() {
assertTrue(SATOSHI.isPositive());
assertFalse(SATOSHI.isNegative());
assertFalse(SATOSHI.isZero());
assertFalse(NEGATIVE_SATOSHI.isPositive());
assertTrue(NEGATIVE_SATOSHI.isNegative());
assertFalse(NEGATIVE_SATOSHI.isZero());
assertFalse(ZERO.isPositive());
assertFalse(ZERO.isNegative());
assertTrue(ZERO.isZero());
assertTrue(valueOf(2).isGreaterThan(valueOf(1)));
assertFalse(valueOf(2).isGreaterThan(valueOf(2)));
assertFalse(valueOf(1).isGreaterThan(valueOf(2)));
assertTrue(valueOf(1).isLessThan(valueOf(2)));
assertFalse(valueOf(2).isLessThan(valueOf(2)));
assertFalse(valueOf(2).isLessThan(valueOf(1)));
}
@Test
public void testToFriendlyString() {
assertEquals("1.00 DOGE", COIN.toFriendlyString());
assertEquals("1.23 DOGE", valueOf(1, 23).toFriendlyString());
assertEquals("0.001 DOGE", COIN.divide(1000).toFriendlyString());
assertEquals("-1.23 DOGE", valueOf(1, 23).negate().toFriendlyString());
}
/**
* Test the bitcoinValueToPlainString amount formatter
*/
@Test
public void testToPlainString() {
assertEquals("0.0015", Coin.valueOf(150000).toPlainString());
assertEquals("1.23", parseCoin("1.23").toPlainString());
assertEquals("0.1", parseCoin("0.1").toPlainString());
assertEquals("1.1", parseCoin("1.1").toPlainString());
assertEquals("21.12", parseCoin("21.12").toPlainString());
assertEquals("321.123", parseCoin("321.123").toPlainString());
assertEquals("4321.1234", parseCoin("4321.1234").toPlainString());
assertEquals("54321.12345", parseCoin("54321.12345").toPlainString());
assertEquals("654321.123456", parseCoin("654321.123456").toPlainString());
assertEquals("7654321.1234567", parseCoin("7654321.1234567").toPlainString());
// More than MAX_MONEY. This will overlow to 0
assertEquals("0", parseCoin(String.valueOf(Long.MAX_VALUE + 1)).toPlainString());
// check there are no trailing zeros
assertEquals("1", parseCoin("1.0").toPlainString());
assertEquals("2", parseCoin("2.00").toPlainString());
assertEquals("3", parseCoin("3.000").toPlainString());
assertEquals("4", parseCoin("4.0000").toPlainString());
assertEquals("5", parseCoin("5.00000").toPlainString());
assertEquals("6", parseCoin("6.000000").toPlainString());
assertEquals("7", parseCoin("7.0000000").toPlainString());
assertEquals("8", parseCoin("8.00000000").toPlainString());
}
}

View File

@ -1,102 +0,0 @@
/**
* Copyright 2012 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 com.dogecoin.dogecoinj.core.AbstractBlockChain.NewBlockType;
import com.dogecoin.dogecoinj.core.Wallet.BalanceType;
import com.dogecoin.dogecoinj.params.MainNetParams;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Test that an example mainnet coinbase transactions can be added to a wallet ok.
*/
public class CoinbaseBlockTest {
static final NetworkParameters params = MainNetParams.get();
// The address for this private key is 1GqtGtn4fctXuKxsVzRPSLmYWN1YioLi9y.
private static final String MINING_PRIVATE_KEY = "5JDxPrBRghF1EvSBjDigywqfmAjpHPmTJxYtQTYJxJRHLLQA4mG";
private static final int BLOCK_OF_INTEREST = 169482;
private static final int BLOCK_LENGTH_AS_HEX = 37357;
private static final long BLOCK_NONCE = 3973947400L;
private static final Coin BALANCE_AFTER_BLOCK = Coin.valueOf(22223642);
@Test
public void testReceiveCoinbaseTransaction() throws Exception {
// Block 169482 (hash 0000000000000756935f1ee9d5987857b604046f846d3df56d024cdb5f368665)
// contains coinbase transactions that are mining pool shares.
// The private key MINERS_KEY is used to check transactions are received by a wallet correctly.
byte[] blockAsBytes = getBytes(getClass().getResourceAsStream("block169482.dat"));
// Create block 169482.
Block block = new Block(params, blockAsBytes);
// Check block.
assertNotNull(block);
block.verify();
assertEquals(BLOCK_NONCE, block.getNonce());
StoredBlock storedBlock = new StoredBlock(block, BigInteger.ONE, BLOCK_OF_INTEREST); // Nonsense work - not used in test.
// Create a wallet contain the miner's key that receives a spend from a coinbase.
ECKey miningKey = (new DumpedPrivateKey(params, MINING_PRIVATE_KEY)).getKey();
assertNotNull(miningKey);
Wallet wallet = new Wallet(params);
wallet.importKey(miningKey);
// Initial balance should be zero by construction.
assertEquals(Coin.ZERO, wallet.getBalance());
// Give the wallet the first transaction in the block - this is the coinbase tx.
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
wallet.receiveFromBlock(transactions.get(0), storedBlock, NewBlockType.BEST_CHAIN, 0);
// Coinbase transaction should have been received successfully but be unavailable to spend (too young).
assertEquals(BALANCE_AFTER_BLOCK, wallet.getBalance(BalanceType.ESTIMATED));
assertEquals(Coin.ZERO, wallet.getBalance(BalanceType.AVAILABLE));
}
/**
* Returns the contents of the InputStream as a byte array.
*/
private byte[] getBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int numberRead;
byte[] data = new byte[BLOCK_LENGTH_AS_HEX];
while ((numberRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, numberRead);
}
buffer.flush();
return buffer.toByteArray();
}
}

View File

@ -1,464 +0,0 @@
/**
* Copyright 2011 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.core.ECKey.ECDSASignature;
import com.dogecoin.dogecoinj.crypto.EncryptedData;
import com.dogecoin.dogecoinj.crypto.KeyCrypter;
import com.dogecoin.dogecoinj.crypto.KeyCrypterScrypt;
import com.dogecoin.dogecoinj.crypto.TransactionSignature;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.dogecoin.dogecoinj.wallet.Protos;
import com.dogecoin.dogecoinj.wallet.Protos.ScryptParameters;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static com.dogecoin.dogecoinj.core.Utils.reverseBytes;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class ECKeyTest {
private static final Logger log = LoggerFactory.getLogger(ECKeyTest.class);
private SecureRandom secureRandom;
private KeyCrypter keyCrypter;
private static CharSequence PASSWORD1 = "my hovercraft has eels";
private static CharSequence WRONG_PASSWORD = "it is a snowy day today";
@Before
public void setUp() throws Exception {
secureRandom = new SecureRandom();
byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH];
secureRandom.nextBytes(salt);
Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt));
ScryptParameters scryptParameters = scryptParametersBuilder.build();
keyCrypter = new KeyCrypterScrypt(scryptParameters);
BriefLogFormatter.init();
}
@Test
public void sValue() throws Exception {
// Check that we never generate an S value that is larger than half the curve order. This avoids a malleability
// issue that can allow someone to change a transaction [hash] without invalidating the signature.
final int ITERATIONS = 10;
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(ITERATIONS));
List<ListenableFuture<ECKey.ECDSASignature>> sigFutures = Lists.newArrayList();
final ECKey key = new ECKey();
for (byte i = 0; i < ITERATIONS; i++) {
final Sha256Hash hash = Sha256Hash.create(new byte[]{i});
sigFutures.add(executor.submit(new Callable<ECKey.ECDSASignature>() {
@Override
public ECKey.ECDSASignature call() throws Exception {
return key.sign(hash);
}
}));
}
List<ECKey.ECDSASignature> sigs = Futures.allAsList(sigFutures).get();
for (ECKey.ECDSASignature signature : sigs) {
assertTrue(signature.isCanonical());
}
final ECDSASignature first = sigs.get(0);
final ECKey.ECDSASignature duplicate = new ECKey.ECDSASignature(first.r, first.s);
assertEquals(first, duplicate);
assertEquals(first.hashCode(), duplicate.hashCode());
final ECKey.ECDSASignature highS = new ECKey.ECDSASignature(first.r, ECKey.CURVE.getN().subtract(first.s));
assertFalse(highS.isCanonical());
}
@Test
public void testSignatures() throws Exception {
// Test that we can construct an ECKey from a private key (deriving the public from the private), then signing
// a message with it.
BigInteger privkey = new BigInteger(1, HEX.decode("180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19"));
ECKey key = ECKey.fromPrivate(privkey);
byte[] output = key.sign(Sha256Hash.ZERO_HASH).encodeToDER();
assertTrue(key.verify(Sha256Hash.ZERO_HASH.getBytes(), output));
// Test interop with a signature from elsewhere.
byte[] sig = HEX.decode(
"3046022100dffbc26774fc841bbe1c1362fd643609c6e42dcb274763476d87af2c0597e89e022100c59e3c13b96b316cae9fa0ab0260612c7a133a6fe2b3445b6bf80b3123bf274d");
assertTrue(key.verify(Sha256Hash.ZERO_HASH.getBytes(), sig));
}
@Test
public void testASN1Roundtrip() throws Exception {
byte[] privkeyASN1 = HEX.decode(
"3082011302010104205c0b98e524ad188ddef35dc6abba13c34a351a05409e5d285403718b93336a4aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a144034200042af7a2aafe8dafd7dc7f9cfb58ce09bda7dce28653ab229b98d1d3d759660c672dd0db18c8c2d76aa470448e876fc2089ab1354c01a6e72cefc50915f4a963ee");
ECKey decodedKey = ECKey.fromASN1(privkeyASN1);
// Now re-encode and decode the ASN.1 to see if it is equivalent (it does not produce the exact same byte
// sequence, some integers are padded now).
ECKey roundtripKey = ECKey.fromASN1(decodedKey.toASN1());
assertArrayEquals(decodedKey.getPrivKeyBytes(), roundtripKey.getPrivKeyBytes());
for (ECKey key : new ECKey[] {decodedKey, roundtripKey}) {
byte[] message = reverseBytes(HEX.decode(
"11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a"));
byte[] output = key.sign(new Sha256Hash(message)).encodeToDER();
assertTrue(key.verify(message, output));
output = HEX.decode(
"304502206faa2ebc614bf4a0b31f0ce4ed9012eb193302ec2bcaccc7ae8bb40577f47549022100c73a1a1acc209f3f860bf9b9f5e13e9433db6f8b7bd527a088a0e0cd0a4c83e9");
assertTrue(key.verify(message, output));
}
// Try to sign with one key and verify with the other.
byte[] message = reverseBytes(HEX.decode(
"11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a"));
assertTrue(roundtripKey.verify(message, decodedKey.sign(new Sha256Hash(message)).encodeToDER()));
assertTrue(decodedKey.verify(message, roundtripKey.sign(new Sha256Hash(message)).encodeToDER()));
}
@Test
public void testKeyPairRoundtrip() throws Exception {
byte[] privkeyASN1 = HEX.decode(
"3082011302010104205c0b98e524ad188ddef35dc6abba13c34a351a05409e5d285403718b93336a4aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a144034200042af7a2aafe8dafd7dc7f9cfb58ce09bda7dce28653ab229b98d1d3d759660c672dd0db18c8c2d76aa470448e876fc2089ab1354c01a6e72cefc50915f4a963ee");
ECKey decodedKey = ECKey.fromASN1(privkeyASN1);
// Now re-encode and decode the ASN.1 to see if it is equivalent (it does not produce the exact same byte
// sequence, some integers are padded now).
ECKey roundtripKey =
ECKey.fromPrivateAndPrecalculatedPublic(decodedKey.getPrivKey(), decodedKey.getPubKeyPoint());
for (ECKey key : new ECKey[] {decodedKey, roundtripKey}) {
byte[] message = reverseBytes(HEX.decode(
"11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a"));
byte[] output = key.sign(new Sha256Hash(message)).encodeToDER();
assertTrue(key.verify(message, output));
output = HEX.decode(
"304502206faa2ebc614bf4a0b31f0ce4ed9012eb193302ec2bcaccc7ae8bb40577f47549022100c73a1a1acc209f3f860bf9b9f5e13e9433db6f8b7bd527a088a0e0cd0a4c83e9");
assertTrue(key.verify(message, output));
}
// Try to sign with one key and verify with the other.
byte[] message = reverseBytes(HEX.decode(
"11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a"));
assertTrue(roundtripKey.verify(message, decodedKey.sign(new Sha256Hash(message)).encodeToDER()));
assertTrue(decodedKey.verify(message, roundtripKey.sign(new Sha256Hash(message)).encodeToDER()));
// Verify bytewise equivalence of public keys (i.e. compression state is preserved)
ECKey key = new ECKey();
ECKey key2 = ECKey.fromASN1(key.toASN1());
assertArrayEquals(key.getPubKey(), key2.getPubKey());
}
@Test
public void base58Encoding() throws Exception {
String addr = "mqAJmaxMcG5pPHHc3H3NtyXzY7kGbJLuMF";
String privkey = "92shANodC6Y4evT5kFzjNFQAdjqTtHAnDTLzqBBq4BbKUPyx6CD";
ECKey key = new DumpedPrivateKey(TestNet3Params.get(), privkey).getKey();
assertEquals(privkey, key.getPrivateKeyEncoded(TestNet3Params.get()).toString());
assertEquals(addr, key.toAddress(TestNet3Params.get()).toString());
}
@Test
public void base58Encoding_leadingZero() throws Exception {
String privkey = "91axuYLa8xK796DnBXXsMbjuc8pDYxYgJyQMvFzrZ6UfXaGYuqL";
ECKey key = new DumpedPrivateKey(TestNet3Params.get(), privkey).getKey();
assertEquals(privkey, key.getPrivateKeyEncoded(TestNet3Params.get()).toString());
assertEquals(0, key.getPrivKeyBytes()[0]);
}
@Test
public void base58Encoding_stress() throws Exception {
// Replace the loop bound with 1000 to get some keys with leading zero byte
for (int i = 0 ; i < 20 ; i++) {
ECKey key = new ECKey();
ECKey key1 = new DumpedPrivateKey(TestNet3Params.get(),
key.getPrivateKeyEncoded(TestNet3Params.get()).toString()).getKey();
assertEquals(Utils.HEX.encode(key.getPrivKeyBytes()),
Utils.HEX.encode(key1.getPrivKeyBytes()));
}
}
@Test
public void signTextMessage() throws Exception {
ECKey key = new ECKey();
String message = "聡中本";
String signatureBase64 = key.signMessage(message);
log.info("Message signed with " + key.toAddress(MainNetParams.get()) + ": " + signatureBase64);
// Should verify correctly.
key.verifyMessage(message, signatureBase64);
try {
key.verifyMessage("Evil attacker says hello!", signatureBase64);
fail();
} catch (SignatureException e) {
// OK.
}
}
@Test
public void verifyMessage() throws Exception {
// Test vector generated by Bitcoin-Qt.
String message = "hello";
String sigBase64 = "HxNZdo6ggZ41hd3mM3gfJRqOQPZYcO8z8qdX2BwmpbF11CaOQV+QiZGGQxaYOncKoNW61oRuSMMF8udfK54XqI8=";
Address expectedAddress = new Address(MainNetParams.get(), "14YPSNPi6NSXnUxtPAsyJSuw3pv7AU3Cag");
ECKey key = ECKey.signedMessageToKey(message, sigBase64);
Address gotAddress = key.toAddress(MainNetParams.get());
assertEquals(expectedAddress, gotAddress);
}
@Test
public void keyRecovery() throws Exception {
ECKey key = new ECKey();
String message = "Hello World!";
Sha256Hash hash = Sha256Hash.create(message.getBytes());
ECKey.ECDSASignature sig = key.sign(hash);
key = ECKey.fromPublicOnly(key.getPubKeyPoint());
boolean found = false;
for (int i = 0; i < 4; i++) {
ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, true);
checkNotNull(key2);
if (key.equals(key2)) {
found = true;
break;
}
}
assertTrue(found);
}
@Test
public void testUnencryptedCreate() throws Exception {
Utils.setMockClock();
ECKey key = new ECKey();
long time = key.getCreationTimeSeconds();
assertNotEquals(0, time);
assertTrue(!key.isEncrypted());
byte[] originalPrivateKeyBytes = key.getPrivKeyBytes();
ECKey encryptedKey = key.encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
assertEquals(time, encryptedKey.getCreationTimeSeconds());
assertTrue(encryptedKey.isEncrypted());
assertNull(encryptedKey.getSecretBytes());
key = encryptedKey.decrypt(keyCrypter.deriveKey(PASSWORD1));
assertTrue(!key.isEncrypted());
assertArrayEquals(originalPrivateKeyBytes, key.getPrivKeyBytes());
}
@Test
public void testEncryptedCreate() throws Exception {
ECKey unencryptedKey = new ECKey();
byte[] originalPrivateKeyBytes = checkNotNull(unencryptedKey.getPrivKeyBytes());
log.info("Original private key = " + Utils.HEX.encode(originalPrivateKeyBytes));
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(unencryptedKey.getPrivKeyBytes(), keyCrypter.deriveKey(PASSWORD1));
ECKey encryptedKey = ECKey.fromEncrypted(encryptedPrivateKey, keyCrypter, unencryptedKey.getPubKey());
assertTrue(encryptedKey.isEncrypted());
assertNull(encryptedKey.getSecretBytes());
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter.deriveKey(PASSWORD1));
assertTrue(!rebornUnencryptedKey.isEncrypted());
assertArrayEquals(originalPrivateKeyBytes, rebornUnencryptedKey.getPrivKeyBytes());
}
@Test
public void testEncryptionIsReversible() throws Exception {
ECKey originalUnencryptedKey = new ECKey();
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(originalUnencryptedKey.getPrivKeyBytes(), keyCrypter.deriveKey(PASSWORD1));
ECKey encryptedKey = ECKey.fromEncrypted(encryptedPrivateKey, keyCrypter, originalUnencryptedKey.getPubKey());
// The key should be encrypted
assertTrue("Key not encrypted at start", encryptedKey.isEncrypted());
// Check that the key can be successfully decrypted back to the original.
assertTrue("Key encryption is not reversible but it should be", ECKey.encryptionIsReversible(originalUnencryptedKey, encryptedKey, keyCrypter, keyCrypter.deriveKey(PASSWORD1)));
// Check that key encryption is not reversible if a password other than the original is used to generate the AES key.
assertTrue("Key encryption is reversible with wrong password", !ECKey.encryptionIsReversible(originalUnencryptedKey, encryptedKey, keyCrypter, keyCrypter.deriveKey(WRONG_PASSWORD)));
// Change one of the encrypted key bytes (this is to simulate a faulty keyCrypter).
// Encryption should not be reversible
byte[] goodEncryptedPrivateKeyBytes = encryptedPrivateKey.encryptedBytes;
// Break the encrypted private key and check it is broken.
byte[] badEncryptedPrivateKeyBytes = new byte[goodEncryptedPrivateKeyBytes.length];
encryptedPrivateKey = new EncryptedData(encryptedPrivateKey.initialisationVector, badEncryptedPrivateKeyBytes);
ECKey badEncryptedKey = ECKey.fromEncrypted(encryptedPrivateKey, keyCrypter, originalUnencryptedKey.getPubKey());
assertTrue("Key encryption is reversible with faulty encrypted bytes", !ECKey.encryptionIsReversible(originalUnencryptedKey, badEncryptedKey, keyCrypter, keyCrypter.deriveKey(PASSWORD1)));
}
@Test
public void testToString() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
NetworkParameters params = MainNetParams.get();
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false, isPubKeyOnly=false}", key.toString());
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv HEX=000000000000000000000000000000000000000000000000000000000000000a, priv WIF=5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreBoNWTw6, isEncrypted=false, isPubKeyOnly=false}", key.toStringWithPrivate(params));
}
@Test
public void testGetPrivateKeyAsHex() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
assertEquals("000000000000000000000000000000000000000000000000000000000000000a", key.getPrivateKeyAsHex());
}
@Test
public void testGetPublicKeyAsHex() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
assertEquals("04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7", key.getPublicKeyAsHex());
}
@Test
public void keyRecoveryWithEncryptedKey() throws Exception {
ECKey unencryptedKey = new ECKey();
KeyParameter aesKey = keyCrypter.deriveKey(PASSWORD1);
ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter, aesKey);
String message = "Goodbye Jupiter!";
Sha256Hash hash = Sha256Hash.create(message.getBytes());
ECKey.ECDSASignature sig = encryptedKey.sign(hash, aesKey);
unencryptedKey = ECKey.fromPublicOnly(unencryptedKey.getPubKeyPoint());
boolean found = false;
for (int i = 0; i < 4; i++) {
ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, true);
checkNotNull(key2);
if (unencryptedKey.equals(key2)) {
found = true;
break;
}
}
assertTrue(found);
}
@Test
public void roundTripDumpedPrivKey() throws Exception {
ECKey key = new ECKey();
assertTrue(key.isCompressed());
NetworkParameters params = UnitTestParams.get();
String base58 = key.getPrivateKeyEncoded(params).toString();
ECKey key2 = new DumpedPrivateKey(params, base58).getKey();
assertTrue(key2.isCompressed());
assertTrue(Arrays.equals(key.getPrivKeyBytes(), key2.getPrivKeyBytes()));
assertTrue(Arrays.equals(key.getPubKey(), key2.getPubKey()));
}
@Test
public void clear() throws Exception {
ECKey unencryptedKey = new ECKey();
ECKey encryptedKey = (new ECKey()).encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
checkSomeBytesAreNonZero(unencryptedKey.getPrivKeyBytes());
// The encryptedPrivateKey should be null in an unencrypted ECKey anyhow but check all the same.
assertTrue(unencryptedKey.getEncryptedPrivateKey() == null);
checkSomeBytesAreNonZero(encryptedKey.getSecretBytes());
checkSomeBytesAreNonZero(encryptedKey.getEncryptedPrivateKey().encryptedBytes);
checkSomeBytesAreNonZero(encryptedKey.getEncryptedPrivateKey().initialisationVector);
}
@Test
public void testCanonicalSigs() throws Exception {
// Tests the canonical sigs from the reference client unit tests
InputStream in = getClass().getResourceAsStream("sig_canonical.json");
// Poor man's JSON parser (because pulling in a lib for this is overkill)
while (in.available() > 0) {
while (in.available() > 0 && in.read() != '"') ;
if (in.available() < 1)
break;
StringBuilder sig = new StringBuilder();
int c;
while (in.available() > 0 && (c = in.read()) != '"')
sig.append((char)c);
assertTrue(TransactionSignature.isEncodingCanonical(HEX.decode(sig.toString())));
}
in.close();
}
@Test
public void testNonCanonicalSigs() throws Exception {
// Tests the noncanonical sigs from the reference client unit tests
InputStream in = getClass().getResourceAsStream("sig_noncanonical.json");
// Poor man's JSON parser (because pulling in a lib for this is overkill)
while (in.available() > 0) {
while (in.available() > 0 && in.read() != '"') ;
if (in.available() < 1)
break;
StringBuilder sig = new StringBuilder();
int c;
while (in.available() > 0 && (c = in.read()) != '"')
sig.append((char)c);
try {
final String sigStr = sig.toString();
assertFalse(TransactionSignature.isEncodingCanonical(HEX.decode(sigStr)));
} catch (IllegalArgumentException e) {
// Expected for non-hex strings in the JSON that we should ignore
}
}
in.close();
}
@Test
public void testCreatedSigAndPubkeyAreCanonical() throws Exception {
// Tests that we will not generate non-canonical pubkeys or signatures
// We dump failed data to error log because this test is not expected to be deterministic
ECKey key = new ECKey();
if (!ECKey.isPubKeyCanonical(key.getPubKey())) {
log.error(Utils.HEX.encode(key.getPubKey()));
fail();
}
byte[] hash = new byte[32];
new Random().nextBytes(hash);
byte[] sigBytes = key.sign(new Sha256Hash(hash)).encodeToDER();
byte[] encodedSig = Arrays.copyOf(sigBytes, sigBytes.length + 1);
encodedSig[sigBytes.length] = (byte) (Transaction.SigHash.ALL.ordinal() + 1);
if (!TransactionSignature.isEncodingCanonical(encodedSig)) {
log.error(Utils.HEX.encode(sigBytes));
fail();
}
}
private static boolean checkSomeBytesAreNonZero(byte[] bytes) {
if (bytes == null) return false;
for (byte b : bytes) if (b != 0) return true;
return false;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.H2FullPrunedBlockStore;
import org.junit.After;
import java.io.File;
/**
* An H2 implementation of the FullPrunedBlockStoreTest
*/
public class H2FullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest {
@After
public void tearDown() throws Exception {
deleteFiles();
}
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount) throws BlockStoreException {
deleteFiles();
return new H2FullPrunedBlockStore(params, "test", blockCount);
}
private void deleteFiles() {
maybeDelete("test.h2.db");
maybeDelete("test.trace.db");
maybeDelete("test.lock.db");
}
private void maybeDelete(String s) {
new File(s).delete();
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((H2FullPrunedBlockStore)store).resetStore();
}
}

View File

@ -1,529 +0,0 @@
/**
* Copyright 2011 Steve Coughlan.
* 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.store.BlockStore;
import com.dogecoin.dogecoinj.store.MemoryBlockStore;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeBlock;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeTx;
import static org.junit.Assert.*;
public class LazyParseByteCacheTest {
private final byte[] txMessage = HEX.withSeparator(" ", 2).decode(
"f9 be b4 d9 74 78 00 00 00 00 00 00 00 00 00 00" +
"02 01 00 00 e2 93 cd be 01 00 00 00 01 6d bd db" +
"08 5b 1d 8a f7 51 84 f0 bc 01 fa d5 8d 12 66 e9" +
"b6 3b 50 88 19 90 e4 b4 0d 6a ee 36 29 00 00 00" +
"00 8b 48 30 45 02 21 00 f3 58 1e 19 72 ae 8a c7" +
"c7 36 7a 7a 25 3b c1 13 52 23 ad b9 a4 68 bb 3a" +
"59 23 3f 45 bc 57 83 80 02 20 59 af 01 ca 17 d0" +
"0e 41 83 7a 1d 58 e9 7a a3 1b ae 58 4e de c2 8d" +
"35 bd 96 92 36 90 91 3b ae 9a 01 41 04 9c 02 bf" +
"c9 7e f2 36 ce 6d 8f e5 d9 40 13 c7 21 e9 15 98" +
"2a cd 2b 12 b6 5d 9b 7d 59 e2 0a 84 20 05 f8 fc" +
"4e 02 53 2e 87 3d 37 b9 6f 09 d6 d4 51 1a da 8f" +
"14 04 2f 46 61 4a 4c 70 c0 f1 4b ef f5 ff ff ff" +
"ff 02 40 4b 4c 00 00 00 00 00 19 76 a9 14 1a a0" +
"cd 1c be a6 e7 45 8a 7a ba d5 12 a9 d9 ea 1a fb" +
"22 5e 88 ac 80 fa e9 c7 00 00 00 00 19 76 a9 14" +
"0e ab 5b ea 43 6a 04 84 cf ab 12 48 5e fd a0 b7" +
"8b 4e cc 52 88 ac 00 00 00 00");
private final byte[] txMessagePart = HEX.withSeparator(" ", 2).decode(
"08 5b 1d 8a f7 51 84 f0 bc 01 fa d5 8d 12 66 e9" +
"b6 3b 50 88 19 90 e4 b4 0d 6a ee 36 29 00 00 00" +
"00 8b 48 30 45 02 21 00 f3 58 1e 19 72 ae 8a c7" +
"c7 36 7a 7a 25 3b c1 13 52 23 ad b9 a4 68 bb 3a");
private Wallet wallet;
private BlockStore blockStore;
private NetworkParameters unitTestParams;
private byte[] b1Bytes;
private byte[] b1BytesWithHeader;
private byte[] tx1Bytes;
private byte[] tx1BytesWithHeader;
private byte[] tx2Bytes;
private byte[] tx2BytesWithHeader;
private void resetBlockStore() {
blockStore = new MemoryBlockStore(unitTestParams);
}
@Before
public void setUp() throws Exception {
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams);
wallet.freshReceiveKey();
resetBlockStore();
Transaction tx1 = createFakeTx(unitTestParams,
valueOf(2, 0),
wallet.currentReceiveKey().toAddress(unitTestParams));
//add a second input so can test granularity of byte cache.
Transaction prevTx = new Transaction(unitTestParams);
TransactionOutput prevOut = new TransactionOutput(unitTestParams, prevTx, COIN, wallet.currentReceiveKey().toAddress(unitTestParams));
prevTx.addOutput(prevOut);
// Connect it.
tx1.addInput(prevOut);
Transaction tx2 = createFakeTx(unitTestParams, COIN,
new ECKey().toAddress(unitTestParams));
Block b1 = createFakeBlock(blockStore, tx1, tx2).block;
BitcoinSerializer bs = new BitcoinSerializer(unitTestParams);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bs.serialize(tx1, bos);
tx1BytesWithHeader = bos.toByteArray();
tx1Bytes = tx1.bitcoinSerialize();
bos.reset();
bs.serialize(tx2, bos);
tx2BytesWithHeader = bos.toByteArray();
tx2Bytes = tx2.bitcoinSerialize();
bos.reset();
bs.serialize(b1, bos);
b1BytesWithHeader = bos.toByteArray();
b1Bytes = b1.bitcoinSerialize();
}
@Test
public void validateSetup() {
byte[] b1 = new byte[] {1, 1, 1, 2, 3, 4, 5, 6, 7};
byte[] b2 = new byte[] {1, 2, 3};
assertTrue(arrayContains(b1, b2));
assertTrue(arrayContains(txMessage, txMessagePart));
assertTrue(arrayContains(tx1BytesWithHeader, tx1Bytes));
assertTrue(arrayContains(tx2BytesWithHeader, tx2Bytes));
assertTrue(arrayContains(b1BytesWithHeader, b1Bytes));
assertTrue(arrayContains(b1BytesWithHeader, tx1Bytes));
assertTrue(arrayContains(b1BytesWithHeader, tx2Bytes));
assertFalse(arrayContains(tx1BytesWithHeader, b1Bytes));
}
@Test
public void testTransactionsLazyRetain() throws Exception {
testTransaction(MainNetParams.get(), txMessage, false, true, true);
testTransaction(unitTestParams, tx1BytesWithHeader, false, true, true);
testTransaction(unitTestParams, tx2BytesWithHeader, false, true, true);
}
@Test
public void testTransactionsLazyNoRetain() throws Exception {
testTransaction(MainNetParams.get(), txMessage, false, true, false);
testTransaction(unitTestParams, tx1BytesWithHeader, false, true, false);
testTransaction(unitTestParams, tx2BytesWithHeader, false, true, false);
}
@Test
public void testTransactionsNoLazyNoRetain() throws Exception {
testTransaction(MainNetParams.get(), txMessage, false, false, false);
testTransaction(unitTestParams, tx1BytesWithHeader, false, false, false);
testTransaction(unitTestParams, tx2BytesWithHeader, false, false, false);
}
@Test
public void testTransactionsNoLazyRetain() throws Exception {
testTransaction(MainNetParams.get(), txMessage, false, false, true);
testTransaction(unitTestParams, tx1BytesWithHeader, false, false, true);
testTransaction(unitTestParams, tx2BytesWithHeader, false, false, true);
}
@Test
public void testBlockAll() throws Exception {
testBlock(b1BytesWithHeader, false, false, false);
testBlock(b1BytesWithHeader, false, true, true);
testBlock(b1BytesWithHeader, false, true, false);
testBlock(b1BytesWithHeader, false, false, true);
}
public void testBlock(byte[] blockBytes, boolean isChild, boolean lazy, boolean retain) throws Exception {
//reference serializer to produce comparison serialization output after changes to
//message structure.
BitcoinSerializer bsRef = new BitcoinSerializer(unitTestParams, false, false);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, lazy, retain);
Block b1;
Block bRef;
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//verify our reference BitcoinSerializer produces matching byte array.
bos.reset();
bsRef.serialize(bRef, bos);
assertTrue(Arrays.equals(bos.toByteArray(), blockBytes));
//check lazy and retain status survive both before and after a serialization
assertEquals(!lazy, b1.isParsedTransactions());
assertEquals(!lazy, b1.isParsedHeader());
if (b1.isParsedHeader())
assertEquals(retain, b1.isHeaderBytesValid());
if (b1.isParsedTransactions())
assertEquals(retain, b1.isTransactionBytesValid());
serDeser(bs, b1, blockBytes, null, null);
assertEquals(!lazy, b1.isParsedTransactions());
assertEquals(!lazy, b1.isParsedHeader());
if (b1.isParsedHeader())
assertEquals(retain, b1.isHeaderBytesValid());
if (b1.isParsedTransactions())
assertEquals(retain, b1.isTransactionBytesValid());
//compare to ref block
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
//retrieve a value from a child
b1.getTransactions();
assertTrue(b1.isParsedTransactions());
if (b1.getTransactions().size() > 0) {
assertTrue(b1.isParsedTransactions());
Transaction tx1 = b1.getTransactions().get(0);
//this will always be true for all children of a block once they are retrieved.
//the tx child inputs/outputs may not be parsed however.
//no longer forced to parse if length not provided.
//assertEquals(true, tx1.isParsed());
if (tx1.isParsed())
assertEquals(retain, tx1.isCached());
else
assertTrue(tx1.isCached());
//does it still match ref block?
serDeser(bs, b1, bos.toByteArray(), null, null);
}
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//retrieve a value from header
b1.getDifficultyTarget();
assertTrue(b1.isParsedHeader());
assertEquals(lazy, !b1.isParsedTransactions());
//does it still match ref block?
serDeser(bs, b1, bos.toByteArray(), null, null);
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//retrieve a value from a child and header
b1.getDifficultyTarget();
assertTrue(b1.isParsedHeader());
assertEquals(lazy, !b1.isParsedTransactions());
b1.getTransactions();
assertTrue(b1.isParsedTransactions());
if (b1.getTransactions().size() > 0) {
assertTrue(b1.isParsedTransactions());
Transaction tx1 = b1.getTransactions().get(0);
//no longer forced to parse if length not provided.
//assertEquals(true, tx1.isParsed());
if (tx1.isParsed())
assertEquals(retain, tx1.isCached());
else
assertTrue(tx1.isCached());
}
//does it still match ref block?
serDeser(bs, b1, bos.toByteArray(), null, null);
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//change a value in header
b1.setNonce(23);
bRef.setNonce(23);
assertTrue(b1.isParsedHeader());
assertEquals(lazy, !b1.isParsedTransactions());
assertFalse(b1.isHeaderBytesValid());
if (b1.isParsedTransactions())
assertEquals(retain , b1.isTransactionBytesValid());
else
assertEquals(true, b1.isTransactionBytesValid());
//does it still match ref block?
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//retrieve a value from a child of a child
b1.getTransactions();
if (b1.getTransactions().size() > 0) {
Transaction tx1 = b1.getTransactions().get(0);
TransactionInput tin = tx1.getInputs().get(0);
assertTrue(tx1.isParsed());
assertTrue(b1.isParsedTransactions());
assertEquals(!lazy, b1.isParsedHeader());
assertEquals(!lazy, tin.isParsed());
assertEquals(tin.isParsed() ? retain : true, tin.isCached());
//does it still match ref tx?
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
}
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//add an input
b1.getTransactions();
if (b1.getTransactions().size() > 0) {
Transaction tx1 = b1.getTransactions().get(0);
if (tx1.getInputs().size() > 0) {
tx1.addInput(tx1.getInputs().get(0));
//replicate on reference tx
bRef.getTransactions().get(0).addInput(bRef.getTransactions().get(0).getInputs().get(0));
assertFalse(tx1.isCached());
assertTrue(tx1.isParsed());
assertFalse(b1.isTransactionBytesValid());
assertTrue(b1.isParsedHeader());
//confirm sibling cache status was unaffected
if (tx1.getInputs().size() > 1) {
boolean parsed = tx1.getInputs().get(1).isParsed();
assertEquals(parsed ? retain : true, tx1.getInputs().get(1).isCached());
assertEquals(!lazy, parsed);
}
//this has to be false. Altering a tx invalidates the merkle root.
//when we have seperate merkle caching then the entire header won't need to be
//invalidated.
assertFalse(b1.isHeaderBytesValid());
bos.reset();
bsRef.serialize(bRef, bos);
byte[] source = bos.toByteArray();
//confirm we still match the reference tx.
serDeser(bs, b1, source, null, null);
}
//does it still match ref tx?
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
}
//refresh block
b1 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
Block b2 = (Block) bs.deserialize(ByteBuffer.wrap(blockBytes));
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
Block bRef2 = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
//reparent an input
b1.getTransactions();
if (b1.getTransactions().size() > 0) {
Transaction tx1 = b1.getTransactions().get(0);
Transaction tx2 = b2.getTransactions().get(0);
if (tx1.getInputs().size() > 0) {
TransactionInput fromTx1 = tx1.getInputs().get(0);
tx2.addInput(fromTx1);
//replicate on reference tx
TransactionInput fromTxRef = bRef.getTransactions().get(0).getInputs().get(0);
bRef2.getTransactions().get(0).addInput(fromTxRef);
//b1 hasn't changed but it's no longer in the parent
//chain of fromTx1 so has to have been uncached since it won't be
//notified of changes throught the parent chain anymore.
assertFalse(b1.isTransactionBytesValid());
//b2 should have it's cache invalidated because it has changed.
assertFalse(b2.isTransactionBytesValid());
bos.reset();
bsRef.serialize(bRef2, bos);
byte[] source = bos.toByteArray();
//confirm altered block matches altered ref block.
serDeser(bs, b2, source, null, null);
}
//does unaltered block still match ref block?
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
//how about if we refresh it?
bRef = (Block) bsRef.deserialize(ByteBuffer.wrap(blockBytes));
bos.reset();
bsRef.serialize(bRef, bos);
serDeser(bs, b1, bos.toByteArray(), null, null);
}
}
public void testTransaction(NetworkParameters params, byte[] txBytes, boolean isChild, boolean lazy, boolean retain) throws Exception {
//reference serializer to produce comparison serialization output after changes to
//message structure.
BitcoinSerializer bsRef = new BitcoinSerializer(params, false, false);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
BitcoinSerializer bs = new BitcoinSerializer(params, lazy, retain);
Transaction t1;
Transaction tRef;
t1 = (Transaction) bs.deserialize(ByteBuffer.wrap(txBytes));
tRef = (Transaction) bsRef.deserialize(ByteBuffer.wrap(txBytes));
//verify our reference BitcoinSerializer produces matching byte array.
bos.reset();
bsRef.serialize(tRef, bos);
assertTrue(Arrays.equals(bos.toByteArray(), txBytes));
//check lazy and retain status survive both before and after a serialization
assertEquals(!lazy, t1.isParsed());
if (t1.isParsed())
assertEquals(retain, t1.isCached());
serDeser(bs, t1, txBytes, null, null);
assertEquals(lazy, !t1.isParsed());
if (t1.isParsed())
assertEquals(retain, t1.isCached());
//compare to ref tx
bos.reset();
bsRef.serialize(tRef, bos);
serDeser(bs, t1, bos.toByteArray(), null, null);
//retrieve a value from a child
t1.getInputs();
assertTrue(t1.isParsed());
if (t1.getInputs().size() > 0) {
assertTrue(t1.isParsed());
TransactionInput tin = t1.getInputs().get(0);
assertEquals(!lazy, tin.isParsed());
if (tin.isParsed())
assertEquals(retain, tin.isCached());
//does it still match ref tx?
serDeser(bs, t1, bos.toByteArray(), null, null);
}
//refresh tx
t1 = (Transaction) bs.deserialize(ByteBuffer.wrap(txBytes));
tRef = (Transaction) bsRef.deserialize(ByteBuffer.wrap(txBytes));
//add an input
if (t1.getInputs().size() > 0) {
t1.addInput(t1.getInputs().get(0));
//replicate on reference tx
tRef.addInput(tRef.getInputs().get(0));
assertFalse(t1.isCached());
assertTrue(t1.isParsed());
bos.reset();
bsRef.serialize(tRef, bos);
byte[] source = bos.toByteArray();
//confirm we still match the reference tx.
serDeser(bs, t1, source, null, null);
}
}
private void serDeser(BitcoinSerializer bs, Message message, byte[] sourceBytes, byte[] containedBytes, byte[] containingBytes) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bs.serialize(message, bos);
byte[] b1 = bos.toByteArray();
Message m2 = bs.deserialize(ByteBuffer.wrap(b1));
assertEquals(message, m2);
bos.reset();
bs.serialize(m2, bos);
byte[] b2 = bos.toByteArray();
assertTrue(Arrays.equals(b1, b2));
if (sourceBytes != null) {
assertTrue(arrayContains(sourceBytes, b1));
assertTrue(arrayContains(sourceBytes, b2));
}
if (containedBytes != null) {
assertTrue(arrayContains(b1, containedBytes));
}
if (containingBytes != null) {
assertTrue(arrayContains(containingBytes, b1));
}
}
public static boolean arrayContains(byte[] sup, byte[] sub) {
if (sup.length < sub.length)
return false;
String superstring = Utils.HEX.encode(sup);
String substring = Utils.HEX.encode(sub);
int ind = superstring.indexOf(substring);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < superstring.indexOf(substring); i++)
sb.append(" ");
//System.out.println(superstring);
//System.out.println(sb.append(substring).toString());
//System.out.println();
return ind > -1;
}
}

View File

@ -1,23 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.MemoryFullPrunedBlockStore;
/**
* A MemoryStore implementation of the FullPrunedBlockStoreTest
*/
public class MemoryFullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest
{
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount) throws BlockStoreException
{
return new MemoryFullPrunedBlockStore(params, blockCount);
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException
{
//No-op for memory store, because it's not persistent
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright 2012 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 com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import org.junit.Before;
import org.junit.Test;
import java.net.InetAddress;
import static com.dogecoin.dogecoinj.core.Coin.COIN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class MemoryPoolTest {
private NetworkParameters params = UnitTestParams.get();
private Transaction tx1, tx2;
private PeerAddress address1, address2, address3;
@Before
public void setup() throws Exception {
BriefLogFormatter.init();
tx1 = FakeTxBuilder.createFakeTx(params, COIN, new ECKey().toAddress(params));
tx2 = new Transaction(params, tx1.bitcoinSerialize());
address1 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
address2 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 2 }));
address3 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 3 }));
}
@Test
public void canonicalInstance() throws Exception {
TxConfidenceTable table = new TxConfidenceTable();
// Check that if we repeatedly send it the same transaction but with different objects, we get back the same
// canonical instance with the confidences update.
assertEquals(0, table.numBroadcastPeers(tx1.getHash()));
assertEquals(tx1, table.seen(tx1, address1));
assertEquals(1, tx1.getConfidence().numBroadcastPeers());
assertEquals(1, table.numBroadcastPeers(tx1.getHash()));
assertEquals(tx1, table.seen(tx2, address2));
assertEquals(2, tx1.getConfidence().numBroadcastPeers());
assertEquals(2, table.numBroadcastPeers(tx1.getHash()));
assertEquals(tx1, table.get(tx1.getHash()));
}
@Test
public void invAndDownload() throws Exception {
TxConfidenceTable table = new TxConfidenceTable();
// Base case: we see a transaction announced twice and then download it. The count is in the confidence object.
assertEquals(0, table.numBroadcastPeers(tx1.getHash()));
table.seen(tx1.getHash(), address1);
assertEquals(1, table.numBroadcastPeers(tx1.getHash()));
assertTrue(table.maybeWasSeen(tx1.getHash()));
table.seen(tx1.getHash(), address2);
assertEquals(2, table.numBroadcastPeers(tx1.getHash()));
Transaction t = table.seen(tx1, address1);
assertEquals(2, t.getConfidence().numBroadcastPeers());
// And now we see another inv.
table.seen(tx1.getHash(), address3);
assertEquals(3, t.getConfidence().numBroadcastPeers());
assertEquals(3, table.numBroadcastPeers(tx1.getHash()));
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2014 Piotr Włodarek
*
* 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 com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Test;
public class MessageTest {
// If readStr() is vulnerable this causes OutOfMemory
@Test(expected = ProtocolException.class)
public void readStrOfExtremeLength() throws Exception {
NetworkParameters params = UnitTestParams.get();
VarInt length = new VarInt(Integer.MAX_VALUE);
byte[] payload = length.encode();
new VarStrMessage(params, payload);
}
static class VarStrMessage extends Message {
public VarStrMessage(NetworkParameters params, byte[] payload) {
super(params, payload, 0);
}
@Override
void parse() throws ProtocolException {
readStr();
}
@Override
protected void parseLite() throws ProtocolException {}
}
// If readBytes() is vulnerable this causes OutOfMemory
@Test(expected = ProtocolException.class)
public void readByteArrayOfExtremeLength() throws Exception {
NetworkParameters params = UnitTestParams.get();
VarInt length = new VarInt(Integer.MAX_VALUE);
byte[] payload = length.encode();
new VarBytesMessage(params, payload);
}
static class VarBytesMessage extends Message {
public VarBytesMessage(NetworkParameters params, byte[] payload) {
super(params, payload, 0);
}
@Override
void parse() throws ProtocolException {
readByteArray();
}
@Override
protected void parseLite() throws ProtocolException {}
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2014 Kalpesh Parmar.
*
* 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 com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.MySQLFullPrunedBlockStore;
import org.junit.After;
import org.junit.Ignore;
/**
* A MySQL implementation of the {@link AbstractFullPrunedBlockChainTest}
*/
@Ignore("enable the mysql driver dependency in the maven POM")
public class MySQLFullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest {
@After
public void tearDown() throws Exception {
((MySQLFullPrunedBlockStore)store).deleteStore();
}
// Replace these with your mysql location/credentials and remove @Ignore to test
private static final String DB_HOSTNAME = "localhost";
private static final String DB_NAME = "bitcoinj_test";
private static final String DB_USERNAME = "bitcoinj";
private static final String DB_PASSWORD = "password";
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount)
throws BlockStoreException {
return new MySQLFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD);
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((MySQLFullPrunedBlockStore)store).resetStore();
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2011 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.MainNetParams;
import org.junit.Test;
import java.net.InetAddress;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.assertEquals;
public class PeerAddressTest
{
@Test
public void testPeerAddressRoundtrip() throws Exception {
// copied verbatim from https://en.bitcoin.it/wiki/Protocol_specification#Network_address
String fromSpec = "010000000000000000000000000000000000ffff0a000001208d";
PeerAddress pa = new PeerAddress(MainNetParams.get(),
HEX.decode(fromSpec), 0, 0);
String reserialized = Utils.HEX.encode(pa.bitcoinSerialize());
assertEquals(reserialized,fromSpec );
}
@Test
public void testBitcoinSerialize() throws Exception {
PeerAddress pa = new PeerAddress(InetAddress.getByName(null), 8333, 0);
assertEquals("000000000000000000000000000000000000ffff7f000001208d",
Utils.HEX.encode(pa.bitcoinSerialize()));
}
}

View File

@ -1,872 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.params.TestNet3Params;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.testing.InboundMessageQueuer;
import com.dogecoin.dogecoinj.testing.TestWithNetworkConnections;
import com.dogecoin.dogecoinj.utils.Threading;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.CancelledKeyException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.*;
import static org.junit.Assert.*;
@RunWith(value = Parameterized.class)
public class PeerTest extends TestWithNetworkConnections {
private Peer peer;
private InboundMessageQueuer writeTarget;
private static final int OTHER_PEER_CHAIN_HEIGHT = 110;
private TxConfidenceTable confidenceTable;
private final AtomicBoolean fail = new AtomicBoolean(false);
@Parameterized.Parameters
public static Collection<ClientType[]> parameters() {
return Arrays.asList(new ClientType[] {ClientType.NIO_CLIENT_MANAGER},
new ClientType[] {ClientType.BLOCKING_CLIENT_MANAGER},
new ClientType[] {ClientType.NIO_CLIENT},
new ClientType[] {ClientType.BLOCKING_CLIENT});
}
public PeerTest(ClientType clientType) {
super(clientType);
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
confidenceTable = blockChain.getContext().getConfidenceTable();
VersionMessage ver = new VersionMessage(unitTestParams, 100);
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 4000);
peer = new Peer(unitTestParams, ver, new PeerAddress(address), blockChain);
peer.addWallet(wallet);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
assertFalse(fail.get());
}
private void connect() throws Exception {
connectWithVersion(70001);
}
private void connectWithVersion(int version) throws Exception {
VersionMessage peerVersion = new VersionMessage(unitTestParams, OTHER_PEER_CHAIN_HEIGHT);
peerVersion.clientVersion = version;
peerVersion.localServices = VersionMessage.NODE_NETWORK;
writeTarget = connect(peer, peerVersion);
}
@Test
public void testAddEventListener() throws Exception {
connect();
PeerEventListener listener = new AbstractPeerEventListener();
peer.addEventListener(listener);
assertTrue(peer.removeEventListener(listener));
assertFalse(peer.removeEventListener(listener));
}
// Check that it runs through the event loop and shut down correctly
@Test
public void shutdown() throws Exception {
closePeer(peer);
}
@Test
public void chainDownloadEnd2End() throws Exception {
// A full end-to-end test of the chain download process, with a new block being solved in the middle.
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = makeSolvedTestBlock(b1);
Block b3 = makeSolvedTestBlock(b2);
Block b4 = makeSolvedTestBlock(b3);
Block b5 = makeSolvedTestBlock(b4);
connect();
peer.startBlockChainDownload();
GetBlocksMessage getblocks = (GetBlocksMessage)outbound(writeTarget);
assertEquals(blockStore.getChainHead().getHeader().getHash(), getblocks.getLocator().get(0));
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// Remote peer sends us an inv with some blocks.
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addBlock(b2);
inv.addBlock(b3);
// We do a getdata on them.
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(b2.getHash(), getdata.getItems().get(0).hash);
assertEquals(b3.getHash(), getdata.getItems().get(1).hash);
assertEquals(2, getdata.getItems().size());
// Remote peer sends us the blocks. The act of doing a getdata for b3 results in getting an inv with just the
// best chain head in it.
inbound(writeTarget, b2);
inbound(writeTarget, b3);
inv = new InventoryMessage(unitTestParams);
inv.addBlock(b5);
// We request the head block.
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(b5.getHash(), getdata.getItems().get(0).hash);
assertEquals(1, getdata.getItems().size());
// Peer sends us the head block. The act of receiving the orphan block triggers a getblocks to fill in the
// rest of the chain.
inbound(writeTarget, b5);
getblocks = (GetBlocksMessage)outbound(writeTarget);
assertEquals(b5.getHash(), getblocks.getStopHash());
assertEquals(b3.getHash(), getblocks.getLocator().get(0));
// At this point another block is solved and broadcast. The inv triggers a getdata but we do NOT send another
// getblocks afterwards, because that would result in us receiving the same set of blocks twice which is a
// timewaste. The getblocks message that would have been generated is set to be the same as the previous
// because we walk backwards down the orphan chain and then discover we already asked for those blocks, so
// nothing is done.
Block b6 = makeSolvedTestBlock(b5);
inv = new InventoryMessage(unitTestParams);
inv.addBlock(b6);
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(1, getdata.getItems().size());
assertEquals(b6.getHash(), getdata.getItems().get(0).hash);
inbound(writeTarget, b6);
assertNull(outbound(writeTarget)); // Nothing is sent at this point.
// We're still waiting for the response to the getblocks (b3,b5) sent above.
inv = new InventoryMessage(unitTestParams);
inv.addBlock(b4);
inv.addBlock(b5);
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(1, getdata.getItems().size());
assertEquals(b4.getHash(), getdata.getItems().get(0).hash);
// We already have b5 from before, so it's not requested again.
inbound(writeTarget, b4);
assertNull(outbound(writeTarget));
// b5 and b6 are now connected by the block chain and we're done.
assertNull(outbound(writeTarget));
closePeer(peer);
}
// Check that an inventory tickle is processed correctly when downloading missing blocks is active.
@Test
public void invTickle() throws Exception {
connect();
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
// Make a missing block.
Block b2 = makeSolvedTestBlock(b1);
Block b3 = makeSolvedTestBlock(b2);
inbound(writeTarget, b3);
InventoryMessage inv = new InventoryMessage(unitTestParams);
InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b3.getHash());
inv.addItem(item);
inbound(writeTarget, inv);
GetBlocksMessage getblocks = (GetBlocksMessage)outbound(writeTarget);
List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
expectedLocator.add(b1.getHash());
expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
assertEquals(getblocks.getLocator(), expectedLocator);
assertEquals(getblocks.getStopHash(), b3.getHash());
assertNull(outbound(writeTarget));
}
// Check that an inv to a peer that is not set to download missing blocks does nothing.
@Test
public void invNoDownload() throws Exception {
// Don't download missing blocks.
peer.setDownloadData(false);
connect();
// Make a missing block that we receive.
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = makeSolvedTestBlock(b1);
// Receive an inv.
InventoryMessage inv = new InventoryMessage(unitTestParams);
InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash());
inv.addItem(item);
inbound(writeTarget, inv);
// Peer does nothing with it.
assertNull(outbound(writeTarget));
}
@Test
public void invDownloadTx() throws Exception {
connect();
peer.setDownloadData(true);
// Make a transaction and tell the peer we have it.
Coin value = COIN;
Transaction tx = createFakeTx(unitTestParams, value, address);
InventoryMessage inv = new InventoryMessage(unitTestParams);
InventoryItem item = new InventoryItem(InventoryItem.Type.Transaction, tx.getHash());
inv.addItem(item);
inbound(writeTarget, inv);
// Peer hasn't seen it before, so will ask for it.
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(1, getdata.getItems().size());
assertEquals(tx.getHash(), getdata.getItems().get(0).hash);
inbound(writeTarget, tx);
// Ask for the dependency, it's not in the mempool (in chain).
getdata = (GetDataMessage) outbound(writeTarget);
inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems()));
pingAndWait(writeTarget);
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
}
@Test
public void invDownloadTxMultiPeer() throws Exception {
// Check co-ordination of which peer to download via the memory pool.
VersionMessage ver = new VersionMessage(unitTestParams, 100);
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 4242);
Peer peer2 = new Peer(unitTestParams, ver, new PeerAddress(address), blockChain);
peer2.addWallet(wallet);
VersionMessage peerVersion = new VersionMessage(unitTestParams, OTHER_PEER_CHAIN_HEIGHT);
peerVersion.clientVersion = 70001;
peerVersion.localServices = VersionMessage.NODE_NETWORK;
connect();
InboundMessageQueuer writeTarget2 = connect(peer2, peerVersion);
// Make a tx and advertise it to one of the peers.
Coin value = COIN;
Transaction tx = createFakeTx(unitTestParams, value, this.address);
InventoryMessage inv = new InventoryMessage(unitTestParams);
InventoryItem item = new InventoryItem(InventoryItem.Type.Transaction, tx.getHash());
inv.addItem(item);
inbound(writeTarget, inv);
// We got a getdata message.
GetDataMessage message = (GetDataMessage)outbound(writeTarget);
assertEquals(1, message.getItems().size());
assertEquals(tx.getHash(), message.getItems().get(0).hash);
assertTrue(confidenceTable.maybeWasSeen(tx.getHash()));
// Advertising to peer2 results in no getdata message.
inbound(writeTarget2, inv);
pingAndWait(writeTarget2);
assertNull(outbound(writeTarget2));
}
// Check that inventory message containing blocks we want is processed correctly.
@Test
public void newBlock() throws Exception {
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
final Block b2 = makeSolvedTestBlock(b1);
// Receive notification of a new block.
final InventoryMessage inv = new InventoryMessage(unitTestParams);
InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash());
inv.addItem(item);
final AtomicInteger newBlockMessagesReceived = new AtomicInteger(0);
connect();
// Round-trip a ping so that we never see the response verack if we attach too quick
pingAndWait(writeTarget);
peer.addEventListener(new AbstractPeerEventListener() {
@Override
public synchronized Message onPreMessageReceived(Peer p, Message m) {
if (p != peer)
fail.set(true);
if (m instanceof Pong)
return m;
int newValue = newBlockMessagesReceived.incrementAndGet();
if (newValue == 1 && !inv.equals(m))
fail.set(true);
else if (newValue == 2 && !b2.equals(m))
fail.set(true);
else if (newValue > 3)
fail.set(true);
return m;
}
@Override
public synchronized void onBlocksDownloaded(Peer p, Block block, int blocksLeft) {
int newValue = newBlockMessagesReceived.incrementAndGet();
if (newValue != 3 || p != peer || !block.equals(b2) || blocksLeft != OTHER_PEER_CHAIN_HEIGHT - 2)
fail.set(true);
}
}, Threading.SAME_THREAD);
long height = peer.getBestHeight();
inbound(writeTarget, inv);
pingAndWait(writeTarget);
assertEquals(height + 1, peer.getBestHeight());
// Response to the getdata message.
inbound(writeTarget, b2);
pingAndWait(writeTarget);
Threading.waitForUserCode();
pingAndWait(writeTarget);
assertEquals(3, newBlockMessagesReceived.get());
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
List<InventoryItem> items = getdata.getItems();
assertEquals(1, items.size());
assertEquals(b2.getHash(), items.get(0).hash);
assertEquals(InventoryItem.Type.Block, items.get(0).type);
}
// Check that it starts downloading the block chain correctly on request.
@Test
public void startBlockChainDownload() throws Exception {
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = makeSolvedTestBlock(b1);
blockChain.add(b2);
connect();
fail.set(true);
peer.addEventListener(new AbstractPeerEventListener() {
@Override
public void onChainDownloadStarted(Peer p, int blocksLeft) {
if (p == peer && blocksLeft == 108)
fail.set(false);
}
}, Threading.SAME_THREAD);
peer.startBlockChainDownload();
List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
expectedLocator.add(b2.getHash());
expectedLocator.add(b1.getHash());
expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
GetBlocksMessage message = (GetBlocksMessage) outbound(writeTarget);
assertEquals(message.getLocator(), expectedLocator);
assertEquals(Sha256Hash.ZERO_HASH, message.getStopHash());
}
@Test
public void getBlock() throws Exception {
connect();
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = makeSolvedTestBlock(b1);
Block b3 = makeSolvedTestBlock(b2);
// Request the block.
Future<Block> resultFuture = peer.getBlock(b3.getHash());
assertFalse(resultFuture.isDone());
// Peer asks for it.
GetDataMessage message = (GetDataMessage) outbound(writeTarget);
assertEquals(message.getItems().get(0).hash, b3.getHash());
assertFalse(resultFuture.isDone());
// Peer receives it.
inbound(writeTarget, b3);
Block b = resultFuture.get();
assertEquals(b, b3);
}
@Test
public void getLargeBlock() throws Exception {
connect();
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = makeSolvedTestBlock(b1);
Transaction t = new Transaction(unitTestParams);
t.addInput(b1.getTransactions().get(0).getOutput(0));
t.addOutput(new TransactionOutput(unitTestParams, t, Coin.ZERO, new byte[Block.MAX_BLOCK_SIZE - 1000]));
b2.addTransaction(t);
// Request the block.
Future<Block> resultFuture = peer.getBlock(b2.getHash());
assertFalse(resultFuture.isDone());
// Peer asks for it.
GetDataMessage message = (GetDataMessage) outbound(writeTarget);
assertEquals(message.getItems().get(0).hash, b2.getHash());
assertFalse(resultFuture.isDone());
// Peer receives it.
inbound(writeTarget, b2);
Block b = resultFuture.get();
assertEquals(b, b2);
}
@Test
public void fastCatchup() throws Exception {
connect();
Utils.setMockClock();
// Check that blocks before the fast catchup point are retrieved using getheaders, and after using getblocks.
// This test is INCOMPLETE because it does not check we handle >2000 blocks correctly.
Block b1 = createFakeBlock(blockStore).block;
blockChain.add(b1);
Utils.rollMockClock(60 * 10); // 10 minutes later.
Block b2 = makeSolvedTestBlock(b1);
b2.setTime(Utils.currentTimeSeconds());
b2.solve();
Utils.rollMockClock(60 * 10); // 10 minutes later.
Block b3 = makeSolvedTestBlock(b2);
b3.setTime(Utils.currentTimeSeconds());
b3.solve();
Utils.rollMockClock(60 * 10);
Block b4 = makeSolvedTestBlock(b3);
b4.setTime(Utils.currentTimeSeconds());
b4.solve();
// Request headers until the last 2 blocks.
peer.setDownloadParameters(Utils.currentTimeSeconds() - (600*2) + 1, false);
peer.startBlockChainDownload();
GetHeadersMessage getheaders = (GetHeadersMessage) outbound(writeTarget);
List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
expectedLocator.add(b1.getHash());
expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
assertEquals(getheaders.getLocator(), expectedLocator);
assertEquals(getheaders.getStopHash(), Sha256Hash.ZERO_HASH);
// Now send all the headers.
HeadersMessage headers = new HeadersMessage(unitTestParams, b2.cloneAsHeader(),
b3.cloneAsHeader(), b4.cloneAsHeader());
// We expect to be asked for b3 and b4 again, but this time, with a body.
expectedLocator.clear();
expectedLocator.add(b2.getHash());
expectedLocator.add(b1.getHash());
expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
inbound(writeTarget, headers);
GetBlocksMessage getblocks = (GetBlocksMessage) outbound(writeTarget);
assertEquals(expectedLocator, getblocks.getLocator());
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// We're supposed to get an inv here.
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addItem(new InventoryItem(InventoryItem.Type.Block, b3.getHash()));
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(b3.getHash(), getdata.getItems().get(0).hash);
// All done.
inbound(writeTarget, b3);
pingAndWait(writeTarget);
closePeer(peer);
}
@Test
public void pingPong() throws Exception {
connect();
Utils.setMockClock();
// No ping pong happened yet.
assertEquals(Long.MAX_VALUE, peer.getLastPingTime());
assertEquals(Long.MAX_VALUE, peer.getPingTime());
ListenableFuture<Long> future = peer.ping();
assertEquals(Long.MAX_VALUE, peer.getLastPingTime());
assertEquals(Long.MAX_VALUE, peer.getPingTime());
assertFalse(future.isDone());
Ping pingMsg = (Ping) outbound(writeTarget);
Utils.rollMockClock(5);
// The pong is returned.
inbound(writeTarget, new Pong(pingMsg.getNonce()));
pingAndWait(writeTarget);
assertTrue(future.isDone());
long elapsed = future.get();
assertTrue("" + elapsed, elapsed > 1000);
assertEquals(elapsed, peer.getLastPingTime());
assertEquals(elapsed, peer.getPingTime());
// Do it again and make sure it affects the average.
future = peer.ping();
pingMsg = (Ping) outbound(writeTarget);
Utils.rollMockClock(50);
inbound(writeTarget, new Pong(pingMsg.getNonce()));
elapsed = future.get();
assertEquals(elapsed, peer.getLastPingTime());
assertEquals(7250, peer.getPingTime());
}
@Test
public void recursiveDependencyDownloadDisabled() throws Exception {
peer.setDownloadTxDependencies(false);
connect();
// Check that if we request dependency download to be disabled and receive a relevant tx, things work correctly.
Transaction tx = FakeTxBuilder.createFakeTx(unitTestParams, COIN, address);
final Transaction[] result = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
result[0] = tx;
}
});
inbound(writeTarget, tx);
pingAndWait(writeTarget);
assertEquals(tx, result[0]);
}
@Test
public void recursiveDependencyDownload() throws Exception {
// Using ping or notfound?
connectWithVersion(70001);
// Check that we can download all dependencies of an unconfirmed relevant transaction from the mempool.
ECKey to = new ECKey();
final Transaction[] onTx = new Transaction[1];
peer.addEventListener(new AbstractPeerEventListener() {
@Override
public void onTransaction(Peer peer1, Transaction t) {
onTx[0] = t;
}
}, Threading.SAME_THREAD);
// Make the some fake transactions in the following graph:
// t1 -> t2 -> [t5]
// -> t3 -> t4 -> [t6]
// -> [t7]
// -> [t8]
// The ones in brackets are assumed to be in the chain and are represented only by hashes.
Transaction t2 = FakeTxBuilder.createFakeTx(unitTestParams, COIN, to);
Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash();
Transaction t4 = FakeTxBuilder.createFakeTx(unitTestParams, COIN, new ECKey());
Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash();
t4.addOutput(COIN, new ECKey());
Transaction t3 = new Transaction(unitTestParams);
t3.addInput(t4.getOutput(0));
t3.addOutput(COIN, new ECKey());
Transaction t1 = new Transaction(unitTestParams);
t1.addInput(t2.getOutput(0));
t1.addInput(t3.getOutput(0));
Sha256Hash someHash = new Sha256Hash("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
t1.addInput(new TransactionInput(unitTestParams, t1, new byte[]{}, new TransactionOutPoint(unitTestParams, 0, someHash)));
Sha256Hash anotherHash = new Sha256Hash("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
t1.addInput(new TransactionInput(unitTestParams, t1, new byte[]{}, new TransactionOutPoint(unitTestParams, 1, anotherHash)));
t1.addOutput(COIN, to);
t1 = FakeTxBuilder.roundTripTransaction(unitTestParams, t1);
t2 = FakeTxBuilder.roundTripTransaction(unitTestParams, t2);
t3 = FakeTxBuilder.roundTripTransaction(unitTestParams, t3);
t4 = FakeTxBuilder.roundTripTransaction(unitTestParams, t4);
// Announce the first one. Wait for it to be downloaded.
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addTransaction(t1);
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
Threading.waitForUserCode();
assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
inbound(writeTarget, t1);
pingAndWait(writeTarget);
assertEquals(t1, onTx[0]);
// We want its dependencies so ask for them.
ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1);
assertFalse(futures.isDone());
// It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(4, getdata.getItems().size());
assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
assertEquals(t3.getHash(), getdata.getItems().get(1).hash);
assertEquals(someHash, getdata.getItems().get(2).hash);
assertEquals(anotherHash, getdata.getItems().get(3).hash);
long nonce = -1;
// For some random reason, t4 is delivered at this point before it's needed - perhaps it was a Bloom filter
// false positive. We do this to check that the mempool is being checked for seen transactions before
// requesting them.
inbound(writeTarget, t4);
// Deliver the requested transactions.
inbound(writeTarget, t2);
inbound(writeTarget, t3);
NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash));
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash));
inbound(writeTarget, notFound);
assertFalse(futures.isDone());
// It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already found t4.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash());
// t5 isn't found and t4 is.
notFound = new NotFoundMessage(unitTestParams);
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5));
inbound(writeTarget, notFound);
assertFalse(futures.isDone());
// Continue to explore the t4 branch and ask for t6, which is in the chain.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t6, getdata.getItems().get(0).hash);
notFound = new NotFoundMessage(unitTestParams);
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6));
inbound(writeTarget, notFound);
pingAndWait(writeTarget);
// That's it, we explored the entire tree.
assertTrue(futures.isDone());
List<Transaction> results = futures.get();
assertTrue(results.contains(t2));
assertTrue(results.contains(t3));
assertTrue(results.contains(t4));
}
@Test
public void timeLockedTransactionNew() throws Exception {
connectWithVersion(70001);
// Test that if we receive a relevant transaction that has a lock time, it doesn't result in a notification
// until we explicitly opt in to seeing those.
Wallet wallet = new Wallet(unitTestParams);
ECKey key = wallet.freshReceiveKey();
peer.addWallet(wallet);
final Transaction[] vtx = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
vtx[0] = tx;
}
});
// Send a normal relevant transaction, it's received correctly.
Transaction t1 = FakeTxBuilder.createFakeTx(unitTestParams, COIN, key);
inbound(writeTarget, t1);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems()));
pingAndWait(writeTarget);
Threading.waitForUserCode();
assertNotNull(vtx[0]);
vtx[0] = null;
// Send a timelocked transaction, nothing happens.
Transaction t2 = FakeTxBuilder.createFakeTx(unitTestParams, valueOf(2, 0), key);
t2.setLockTime(999999);
inbound(writeTarget, t2);
Threading.waitForUserCode();
assertNull(vtx[0]);
// Now we want to hear about them. Send another, we are told about it.
wallet.setAcceptRiskyTransactions(true);
inbound(writeTarget, t2);
getdata = (GetDataMessage) outbound(writeTarget);
inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems()));
pingAndWait(writeTarget);
Threading.waitForUserCode();
assertEquals(t2, vtx[0]);
}
@Test
public void rejectTimeLockedDependency() throws Exception {
// Check that we also verify the lock times of dependencies. Otherwise an attacker could still build a tx that
// looks legitimate and useful but won't actually ever confirm, by sending us a normal tx that spends a
// timelocked tx.
checkTimeLockedDependency(false);
}
@Test
public void acceptTimeLockedDependency() throws Exception {
checkTimeLockedDependency(true);
}
private void checkTimeLockedDependency(boolean shouldAccept) throws Exception {
// Initial setup.
connectWithVersion(70001);
Wallet wallet = new Wallet(unitTestParams);
ECKey key = wallet.freshReceiveKey();
wallet.setAcceptRiskyTransactions(shouldAccept);
peer.addWallet(wallet);
final Transaction[] vtx = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
vtx[0] = tx;
}
});
// t1 -> t2 [locked] -> t3 (not available)
Transaction t2 = new Transaction(unitTestParams);
t2.setLockTime(999999);
// Add a fake input to t3 that goes nowhere.
Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8")));
t2.addInput(new TransactionInput(unitTestParams, t2, new byte[]{}, new TransactionOutPoint(unitTestParams, 0, t3)));
t2.getInput(0).setSequenceNumber(0xDEADBEEF);
t2.addOutput(COIN, new ECKey());
Transaction t1 = new Transaction(unitTestParams);
t1.addInput(t2.getOutput(0));
t1.addOutput(COIN, key); // Make it relevant.
// Announce t1.
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addTransaction(t1);
inbound(writeTarget, inv);
// Send it.
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
inbound(writeTarget, t1);
// Nothing arrived at our event listener yet.
assertNull(vtx[0]);
// We request t2.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
inbound(writeTarget, t2);
// We request t3.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t3, getdata.getItems().get(0).hash);
// Can't find it: bottom of tree.
NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3));
inbound(writeTarget, notFound);
pingAndWait(writeTarget);
Threading.waitForUserCode();
// We're done but still not notified because it was timelocked.
if (shouldAccept)
assertNotNull(vtx[0]);
else
assertNull(vtx[0]);
}
@Test
public void disconnectOldVersions1() throws Exception {
// Set up the connection with an old version.
final SettableFuture<Void> connectedFuture = SettableFuture.create();
final SettableFuture<Void> disconnectedFuture = SettableFuture.create();
peer.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
connectedFuture.set(null);
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
disconnectedFuture.set(null);
}
});
connectWithVersion(500);
// We must wait uninterruptibly here because connect[WithVersion] generates a peer that interrupts the current
// thread when it disconnects.
Uninterruptibles.getUninterruptibly(connectedFuture);
Uninterruptibles.getUninterruptibly(disconnectedFuture);
try {
peer.writeTarget.writeBytes(new byte[1]);
fail();
} catch (IOException e) {
assertTrue((e.getCause() != null && e.getCause() instanceof CancelledKeyException)
|| (e instanceof SocketException && e.getMessage().equals("Socket is closed")));
}
}
@Test
public void exceptionListener() throws Exception {
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
throw new NullPointerException("boo!");
}
});
final Throwable[] throwables = new Throwable[1];
Threading.uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
throwables[0] = throwable;
}
};
// In real usage we're not really meant to adjust the uncaught exception handler after stuff started happening
// but in the unit test environment other tests have just run so the thread is probably still kicking around.
// Force it to crash so it'll be recreated with our new handler.
Threading.USER_THREAD.execute(new Runnable() {
@Override
public void run() {
throw new RuntimeException();
}
});
connect();
Transaction t1 = new Transaction(unitTestParams);
t1.addInput(new TransactionInput(unitTestParams, t1, new byte[]{}));
t1.addOutput(COIN, new ECKey().toAddress(unitTestParams));
Transaction t2 = new Transaction(unitTestParams);
t2.addInput(t1.getOutput(0));
t2.addOutput(COIN, wallet.getChangeAddress());
inbound(writeTarget, t2);
final InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Transaction, t2.getInput(0).getOutpoint().getHash());
final NotFoundMessage nfm = new NotFoundMessage(unitTestParams, Lists.newArrayList(inventoryItem));
inbound(writeTarget, nfm);
pingAndWait(writeTarget);
Threading.waitForUserCode();
assertTrue(throwables[0] instanceof NullPointerException);
Threading.uncaughtExceptionHandler = null;
}
@Test
public void badMessage() throws Exception {
// Bring up an actual network connection and feed it bogus data.
final SettableFuture<Void> result = SettableFuture.create();
Threading.uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
result.setException(throwable);
}
};
connect(); // Writes out a verack+version.
final SettableFuture<Void> peerDisconnected = SettableFuture.create();
writeTarget.peer.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerDisconnected(Peer p, int peerCount) {
peerDisconnected.set(null);
}
});
final NetworkParameters params = TestNet3Params.get();
BitcoinSerializer serializer = new BitcoinSerializer(params);
// Now write some bogus truncated message.
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializer.serialize("inv", new InventoryMessage(params) {
@Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
// Add some hashes.
addItem(new InventoryItem(InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] { 1 })));
addItem(new InventoryItem(InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] { 2 })));
addItem(new InventoryItem(InventoryItem.Type.Transaction, Sha256Hash.create(new byte[] { 3 })));
// Write out a copy that's truncated in the middle.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
super.bitcoinSerializeToStream(bos);
byte[] bits = bos.toByteArray();
bits = Arrays.copyOf(bits, bits.length / 2);
stream.write(bits);
}
}.bitcoinSerialize(), out);
writeTarget.writeTarget.writeBytes(out.toByteArray());
try {
result.get();
fail();
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof ProtocolException);
}
peerDisconnected.get();
try {
peer.writeTarget.writeBytes(new byte[1]);
fail();
} catch (IOException e) {
assertTrue((e.getCause() != null && e.getCause() instanceof CancelledKeyException)
|| (e instanceof SocketException && e.getMessage().equals("Socket is closed")));
}
}
}

View File

@ -1,58 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.store.BlockStoreException;
import com.dogecoin.dogecoinj.store.FullPrunedBlockStore;
import com.dogecoin.dogecoinj.store.PostgresFullPrunedBlockStore;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
/**
* A Postgres implementation of the {@link AbstractFullPrunedBlockChainTest}
*/
@Ignore("enable the postgres driver dependency in the maven POM")
public class PostgresFullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest
{
// Replace these with your postgres location/credentials and remove @Ignore to test
// You can set up a fresh postgres with the command: create user bitcoinj superuser password 'password';
private static final String DB_HOSTNAME = "localhost";
private static final String DB_NAME = "dogecoinj_test";
private static final String DB_USERNAME = "dogecoinj";
private static final String DB_PASSWORD = "password";
private static final String DB_SCHEMA = "blockstore_schema";
// whether to run the test with a schema name
private boolean useSchema = false;
@After
public void tearDown() throws Exception {
((PostgresFullPrunedBlockStore)store).deleteStore();
}
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount)
throws BlockStoreException {
if(useSchema) {
return new PostgresFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD, DB_SCHEMA);
}
else {
return new PostgresFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD);
}
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((PostgresFullPrunedBlockStore)store).resetStore();
}
@Test
public void testFirst100kBlocksWithCustomSchema() throws Exception {
boolean oldSchema = useSchema;
useSchema = true;
try {
super.testFirst100KBlocks();
} finally {
useSchema = oldSchema;
}
}
}

View File

@ -1,69 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.google.common.collect.ImmutableList;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.script.Script;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.script.ScriptOpCodes;
import com.dogecoin.dogecoinj.testing.TestWithWallet;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class TransactionOutputTest extends TestWithWallet {
@Before
public void setUp() throws Exception {
super.setUp();
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void testMultiSigOutputToString() throws Exception {
sendMoneyToWallet(Coin.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
ECKey myKey = new ECKey();
this.wallet.importKey(myKey);
// Simulate another signatory
ECKey otherKey = new ECKey();
// Create multi-sig transaction
Transaction multiSigTransaction = new Transaction(params);
ImmutableList<ECKey> keys = ImmutableList.of(myKey, otherKey);
Script scriptPubKey = ScriptBuilder.createMultiSigOutputScript(2, keys);
multiSigTransaction.addOutput(Coin.COIN, scriptPubKey);
Wallet.SendRequest req = Wallet.SendRequest.forTx(multiSigTransaction);
this.wallet.completeTx(req);
TransactionOutput multiSigTransactionOutput = multiSigTransaction.getOutput(0);
assertThat(multiSigTransactionOutput.toString(), CoreMatchers.containsString("CHECKMULTISIG"));
}
@Test
public void testP2SHOutputScript() throws Exception {
String P2SHAddressString = "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU";
Address P2SHAddress = new Address(MainNetParams.get(), P2SHAddressString);
Script script = ScriptBuilder.createOutputScript(P2SHAddress);
Transaction tx = new Transaction(MainNetParams.get());
tx.addOutput(Coin.COIN, script);
assertEquals(P2SHAddressString, tx.getOutput(0).getAddressFromP2SH(MainNetParams.get()).toString());
}
@Test
public void getAddressTests() throws Exception {
Transaction tx = new Transaction(MainNetParams.get());
Script script = new ScriptBuilder().op(ScriptOpCodes.OP_RETURN).data("hello world!".getBytes()).build();
tx.addOutput(Coin.CENT, script);
assertNull(tx.getOutput(0).getAddressFromP2SH(params));
assertNull(tx.getOutput(0).getAddressFromP2PKHScript(params));
}
}

View File

@ -1,92 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Just check the Transaction.verify() method. Most methods that have complicated logic in Transaction are tested
* elsewhere, e.g. signing and hashing are well exercised by the wallet tests, the full block chain tests and so on.
* The verify method is also exercised by the full block chain tests, but it can also be used by API users alone,
* so we make sure to cover it here as well.
*/
public class TransactionTest {
private static final NetworkParameters PARAMS = UnitTestParams.get();
private Transaction tx;
private Transaction dummy;
public static final Address ADDRESS = new ECKey().toAddress(PARAMS);
@Before
public void setUp() throws Exception {
dummy = FakeTxBuilder.createFakeTx(PARAMS, Coin.COIN, ADDRESS);
tx = new Transaction(PARAMS);
tx.addOutput(Coin.COIN, ADDRESS);
tx.addInput(dummy.getOutput(0));
}
@Test(expected = VerificationException.EmptyInputsOrOutputs.class)
public void emptyOutputs() throws Exception {
tx.clearOutputs();
tx.verify();
}
@Test(expected = VerificationException.EmptyInputsOrOutputs.class)
public void emptyInputs() throws Exception {
tx.clearInputs();
tx.verify();
}
@Test(expected = VerificationException.LargerThanMaxBlockSize.class)
public void tooHuge() throws Exception {
tx.addInput(dummy.getOutput(0)).setScriptBytes(new byte[Block.MAX_BLOCK_SIZE]);
tx.verify();
}
@Test(expected = VerificationException.DuplicatedOutPoint.class)
public void duplicateOutPoint() throws Exception {
TransactionInput input = tx.getInput(0);
input.setScriptBytes(new byte[1]);
tx.addInput(input.duplicateDetached());
tx.verify();
}
@Test(expected = VerificationException.NegativeValueOutput.class)
public void negativeOutput() throws Exception {
tx.getOutput(0).setValue(Coin.NEGATIVE_SATOSHI);
tx.verify();
}
@Test(expected = VerificationException.ExcessiveValue.class)
public void exceedsMaxMoney2() throws Exception {
Coin half = NetworkParameters.MAX_MONEY.divide(2).add(Coin.SATOSHI);
tx.getOutput(0).setValue(half);
tx.addOutput(half, ADDRESS);
tx.verify();
}
@Test(expected = VerificationException.UnexpectedCoinbaseInput.class)
public void coinbaseInputInNonCoinbaseTX() throws Exception {
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[10]).build());
tx.verify();
}
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
public void coinbaseScriptSigTooSmall() throws Exception {
tx.clearInputs();
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().build());
tx.verify();
}
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
public void coinbaseScriptSigTooLarge() throws Exception {
tx.clearInputs();
TransactionInput input = tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[99]).build());
assertEquals(101, input.getScriptBytes().length);
tx.verify();
}
}

View File

@ -1,65 +0,0 @@
/**
* Copyright 2011 Thilo Planz
* 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 com.dogecoin.dogecoinj.core;
import java.math.BigInteger;
import java.util.Date;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class UtilsTest {
@Test
public void testReverseBytes() {
assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, Utils.reverseBytes(new byte[]{5, 4, 3, 2, 1}));
}
@Test
public void testReverseDwordBytes() {
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}, Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, -1));
assertArrayEquals(new byte[]{1, 2, 3, 4}, Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, 4));
assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, 0));
assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[0], 0));
}
@Test
public void testMaxOfMostFreq() throws Exception {
assertEquals(0, Utils.maxOfMostFreq());
assertEquals(0, Utils.maxOfMostFreq(0, 0, 1));
assertEquals(2, Utils.maxOfMostFreq(1, 1, 2, 2));
assertEquals(1, Utils.maxOfMostFreq(1, 1, 2, 2, 1));
assertEquals(-1, Utils.maxOfMostFreq(-1, -1, 2, 2, -1));
}
@Test
public void compactEncoding() throws Exception {
assertEquals(new BigInteger("1234560000", 16), Utils.decodeCompactBits(0x05123456L));
assertEquals(new BigInteger("c0de000000", 16), Utils.decodeCompactBits(0x0600c0de));
assertEquals(0x05123456L, Utils.encodeCompactBits(new BigInteger("1234560000", 16)));
assertEquals(0x0600c0deL, Utils.encodeCompactBits(new BigInteger("c0de000000", 16)));
}
@Test
public void dateTimeFormat() {
assertEquals("2014-11-16T10:54:33Z", Utils.dateTimeFormat(1416135273781L));
assertEquals("2014-11-16T10:54:33Z", Utils.dateTimeFormat(new Date(1416135273781L)));
}
}

View File

@ -1,66 +0,0 @@
/**
* 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 junit.framework.TestCase;
public class VarIntTest extends TestCase {
public void testBytes() throws Exception {
VarInt a = new VarInt(10);
assertEquals(1, a.getSizeInBytes());
assertEquals(1, a.encode().length);
assertEquals(10, new VarInt(a.encode(), 0).value);
}
public void testShorts() throws Exception {
VarInt a = new VarInt(64000);
assertEquals(3, a.getSizeInBytes());
assertEquals(3, a.encode().length);
assertEquals(64000, new VarInt(a.encode(), 0).value);
}
public void testShortFFFF() throws Exception {
VarInt a = new VarInt(0xFFFFL);
assertEquals(3, a.getSizeInBytes());
assertEquals(3, a.encode().length);
assertEquals(0xFFFFL, new VarInt(a.encode(), 0).value);
}
public void testInts() throws Exception {
VarInt a = new VarInt(0xAABBCCDDL);
assertEquals(5, a.getSizeInBytes());
assertEquals(5, a.encode().length);
byte[] bytes = a.encode();
assertEquals(0xAABBCCDDL, 0xFFFFFFFFL & new VarInt(bytes, 0).value);
}
public void testIntFFFFFFFF() throws Exception {
VarInt a = new VarInt(0xFFFFFFFFL);
assertEquals(5, a.getSizeInBytes());
assertEquals(5, a.encode().length);
byte[] bytes = a.encode();
assertEquals(0xFFFFFFFFL, 0xFFFFFFFFL & new VarInt(bytes, 0).value);
}
public void testLong() throws Exception {
VarInt a = new VarInt(0xCAFEBABEDEADBEEFL);
assertEquals(9, a.getSizeInBytes());
assertEquals(9, a.encode().length);
byte[] bytes = a.encode();
assertEquals(0xCAFEBABEDEADBEEFL, new VarInt(bytes, 0).value);
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2012 Matt Corallo
* 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 com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Test;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.assertTrue;
public class VersionMessageTest {
@Test
// Test that we can decode version messages which miss data which some old nodes may not include
public void testDecode() throws Exception {
NetworkParameters params = UnitTestParams.get();
VersionMessage ver = new VersionMessage(params, HEX.decode("71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f0004000000"));
assertTrue(!ver.relayTxesBeforeFilter);
assertTrue(ver.bestHeight == 1024);
assertTrue(ver.subVer.equals("/BitCoinJ:0.7-SNAPSHOT/"));
ver = new VersionMessage(params, HEX.decode("71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f00040000"));
assertTrue(ver.relayTxesBeforeFilter);
assertTrue(ver.bestHeight == 1024);
assertTrue(ver.subVer.equals("/BitCoinJ:0.7-SNAPSHOT/"));
ver = new VersionMessage(params, HEX.decode("71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f"));
assertTrue(ver.relayTxesBeforeFilter);
assertTrue(ver.bestHeight == 0);
assertTrue(ver.subVer.equals("/BitCoinJ:0.7-SNAPSHOT/"));
ver = new VersionMessage(params, HEX.decode("71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000"));
assertTrue(ver.relayTxesBeforeFilter);
assertTrue(ver.bestHeight == 0);
assertTrue(ver.subVer.equals(""));
}
}

View File

@ -1,28 +0,0 @@
package com.dogecoin.dogecoinj.core;
import com.dogecoin.dogecoinj.testing.FooWalletExtension;
import com.dogecoin.dogecoinj.testing.TestWithWallet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class WalletExtensionsTest extends TestWithWallet {
@Before
@Override
public void setUp() throws Exception {
super.setUp();
}
@After
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Test(expected = java.lang.IllegalStateException.class)
public void duplicateWalletExtensionTest() {
wallet.addExtension(new FooWalletExtension("com.whatever.required", true));
wallet.addExtension(new FooWalletExtension("com.whatever.required", true));
}
}

View File

@ -1,185 +0,0 @@
/**
* Copyright 2013 Matija Mazi.
* 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 com.dogecoin.dogecoinj.crypto;
import com.dogecoin.dogecoinj.core.AddressFormatException;
import com.dogecoin.dogecoinj.core.Base58;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.dogecoin.dogecoinj.core.NetworkParameters;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.assertEquals;
/**
* A test with test vectors as per BIP 32 spec: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors
*/
public class BIP32Test {
private static final Logger log = LoggerFactory.getLogger(BIP32Test.class);
HDWTestVector[] tvs = {
new HDWTestVector(
"000102030405060708090a0b0c0d0e0f",
"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
Arrays.asList(
new HDWTestVector.DerivedTestCase(
"Test1 m/0H",
new ChildNumber[]{new ChildNumber(0, true)},
"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
"xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
),
new HDWTestVector.DerivedTestCase(
"Test1 m/0H/1",
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false)},
"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
"xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
),
new HDWTestVector.DerivedTestCase(
"Test1 m/0H/1/2H",
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true)},
"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
),
new HDWTestVector.DerivedTestCase(
"Test1 m/0H/1/2H/2",
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true), new ChildNumber(2, false)},
"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
"xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
),
new HDWTestVector.DerivedTestCase(
"Test1 m/0H/1/2H/2/1000000000",
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true), new ChildNumber(2, false), new ChildNumber(1000000000, false)},
"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
)
)
),
new HDWTestVector(
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
"xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
"xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
Arrays.asList(
new HDWTestVector.DerivedTestCase(
"Test2 m/0",
new ChildNumber[]{new ChildNumber(0, false)},
"xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
"xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
),
new HDWTestVector.DerivedTestCase(
"Test2 m/0/2147483647H",
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true)},
"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
"xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
),
new HDWTestVector.DerivedTestCase(
"Test2 m/0/2147483647H/1",
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false)},
"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
"xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
),
new HDWTestVector.DerivedTestCase(
"Test2 m/0/2147483647H/1/2147483646H",
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false), new ChildNumber(2147483646, true)},
"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
),
new HDWTestVector.DerivedTestCase(
"Test2 m/0/2147483647H/1/2147483646H/2",
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false), new ChildNumber(2147483646, true), new ChildNumber(2, false)},
"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
)
)
)
};
@Test
public void testVector1() throws Exception {
testVector(0);
}
@Test
public void testVector2() throws Exception {
testVector(1);
}
private void testVector(int testCase) throws AddressFormatException {
log.info("======= Test vector {}", testCase);
HDWTestVector tv = tvs[testCase];
NetworkParameters params = MainNetParams.get();
DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(HEX.decode(tv.seed));
assertEquals(testEncode(tv.priv), testEncode(masterPrivateKey.serializePrivB58(params)));
assertEquals(testEncode(tv.pub), testEncode(masterPrivateKey.serializePubB58(params)));
DeterministicHierarchy dh = new DeterministicHierarchy(masterPrivateKey);
for (int i = 0; i < tv.derived.size(); i++) {
HDWTestVector.DerivedTestCase tc = tv.derived.get(i);
log.info("{}", tc.name);
assertEquals(tc.name, String.format("Test%d %s", testCase + 1, tc.getPathDescription()));
int depth = tc.path.length - 1;
DeterministicKey ehkey = dh.deriveChild(Arrays.asList(tc.path).subList(0, depth), false, true, tc.path[depth]);
assertEquals(testEncode(tc.priv), testEncode(ehkey.serializePrivB58(params)));
assertEquals(testEncode(tc.pub), testEncode(ehkey.serializePubB58(params)));
}
}
private String testEncode(String what) throws AddressFormatException {
return HEX.encode(Base58.decodeChecked(what));
}
static class HDWTestVector {
final String seed;
final String priv;
final String pub;
final List<DerivedTestCase> derived;
HDWTestVector(String seed, String priv, String pub, List<DerivedTestCase> derived) {
this.seed = seed;
this.priv = priv;
this.pub = pub;
this.derived = derived;
}
static class DerivedTestCase {
final String name;
final ChildNumber[] path;
final String pub;
final String priv;
DerivedTestCase(String name, ChildNumber[] path, String priv, String pub) {
this.name = name;
this.path = path;
this.pub = pub;
this.priv = priv;
}
String getPathDescription() {
return "m/" + Joiner.on("/").join(Iterables.transform(Arrays.asList(path), Functions.toStringFunction()));
}
}
}
}

View File

@ -1,172 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.crypto;
import com.dogecoin.dogecoinj.core.ECKey;
import com.dogecoin.dogecoinj.crypto.BIP38PrivateKey.BadPassphraseException;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
public class BIP38PrivateKeyTest {
private static final MainNetParams MAINNET = MainNetParams.get();
private static final TestNet3Params TESTNET = TestNet3Params.get();
@Test
public void bip38testvector_noCompression_noEcMultiply_test1() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MAINNET,
"6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg");
ECKey key = encryptedKey.decrypt("TestingOneTwoThree");
assertEquals("5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_noCompression_noEcMultiply_test2() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MAINNET,
"6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq");
ECKey key = encryptedKey.decrypt("Satoshi");
assertEquals("5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_noCompression_noEcMultiply_test3() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MAINNET,
"6PRW5o9FLp4gJDDVqJQKJFTpMvdsSGJxMYHtHaQBF3ooa8mwD69bapcDQn");
StringBuilder passphrase = new StringBuilder();
passphrase.appendCodePoint(0x03d2); // GREEK UPSILON WITH HOOK
passphrase.appendCodePoint(0x0301); // COMBINING ACUTE ACCENT
passphrase.appendCodePoint(0x0000); // NULL
passphrase.appendCodePoint(0x010400); // DESERET CAPITAL LETTER LONG I
passphrase.appendCodePoint(0x01f4a9); // PILE OF POO
ECKey key = encryptedKey.decrypt(passphrase.toString());
assertEquals("5Jajm8eQ22H3pGWLEVCXyvND8dQZhiQhoLJNKjYXk9roUFTMSZ4", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_compression_noEcMultiply_test1() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo");
ECKey key = encryptedKey.decrypt("TestingOneTwoThree");
assertEquals("L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_compression_noEcMultiply_test2() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7");
ECKey key = encryptedKey.decrypt("Satoshi");
assertEquals("KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_ecMultiply_noCompression_noLotAndSequence_test1() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX");
ECKey key = encryptedKey.decrypt("TestingOneTwoThree");
assertEquals("5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_ecMultiply_noCompression_noLotAndSequence_test2() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd");
ECKey key = encryptedKey.decrypt("Satoshi");
assertEquals("5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_ecMultiply_noCompression_lotAndSequence_test1() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j");
ECKey key = encryptedKey.decrypt("MOLON LABE");
assertEquals("5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bip38testvector_ecMultiply_noCompression_lotAndSequence_test2() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MainNetParams.get(),
"6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH");
ECKey key = encryptedKey.decrypt("ΜΟΛΩΝ ΛΑΒΕ");
assertEquals("5KMKKuUmAkiNbA3DazMQiLfDq47qs8MAEThm4yL8R2PhV1ov33D", key.getPrivateKeyEncoded(MAINNET)
.toString());
}
@Test
public void bitcoinpaperwallet_testnet() throws Exception {
// values taken from bitcoinpaperwallet.com
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(TESTNET,
"6PRPhQhmtw6dQu6jD8E1KS4VphwJxBS9Eh9C8FQELcrwN3vPvskv9NKvuL");
ECKey key = encryptedKey.decrypt("password");
assertEquals("93MLfjbY6ugAsLeQfFY6zodDa8izgm1XAwA9cpMbUTwLkDitopg", key.getPrivateKeyEncoded(TESTNET)
.toString());
}
@Test
public void bitaddress_testnet() throws Exception {
// values taken from bitaddress.org
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(TESTNET,
"6PfMmVHn153N3x83Yiy4Nf76dHUkXufe2Adr9Fw5bewrunGNeaw2QCpifb");
ECKey key = encryptedKey.decrypt("password");
assertEquals("91tCpdaGr4Khv7UAuUxa6aMqeN5GcPVJxzLtNsnZHTCndxkRcz2", key.getPrivateKeyEncoded(TESTNET)
.toString());
}
@Test(expected = BadPassphraseException.class)
public void badPassphrase() throws Exception {
BIP38PrivateKey encryptedKey = new BIP38PrivateKey(MAINNET,
"6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg");
encryptedKey.decrypt("BAD");
}
@Test
public void testJavaSerialization() throws Exception {
BIP38PrivateKey key = new BIP38PrivateKey(TESTNET, "6PfMmVHn153N3x83Yiy4Nf76dHUkXufe2Adr9Fw5bewrunGNeaw2QCpifb");
ByteArrayOutputStream os = new ByteArrayOutputStream();
new ObjectOutputStream(os).writeObject(key);
BIP38PrivateKey keyCopy = (BIP38PrivateKey) new ObjectInputStream(new ByteArrayInputStream(os.toByteArray()))
.readObject();
assertEquals(key, keyCopy);
}
@Test
public void cloning() throws Exception {
BIP38PrivateKey a = new BIP38PrivateKey(TESTNET, "6PfMmVHn153N3x83Yiy4Nf76dHUkXufe2Adr9Fw5bewrunGNeaw2QCpifb");
// TODO: Consider overriding clone() in BIP38PrivateKey to narrow the type
BIP38PrivateKey b = (BIP38PrivateKey) a.clone();
assertEquals(a, b);
assertNotSame(a, b);
}
}

View File

@ -1,250 +0,0 @@
/**
* Copyright 2013 Matija Mazi.
* 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 com.dogecoin.dogecoinj.crypto;
import com.dogecoin.dogecoinj.core.ECKey;
import com.dogecoin.dogecoinj.core.NetworkParameters;
import com.dogecoin.dogecoinj.core.Sha256Hash;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.*;
/**
* This test is adapted from Armory's BIP 32 tests.
*/
public class ChildKeyDerivationTest {
private static final int HDW_CHAIN_EXTERNAL = 0;
private static final int HDW_CHAIN_INTERNAL = 1;
@Test
public void testChildKeyDerivation() throws Exception {
String ckdTestVectors[] = {
// test case 1:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"04" + "6a04ab98d9e4774ad806e302dddeb63b" +
"ea16b5cb5f223ee77478e861bb583eb3" +
"36b6fbcb60b5b3d4f1551ac45e5ffc49" +
"36466e7d98f6c7c0ec736539f74691a6",
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
// test case 2:
"be05d9ded0a73f81b814c93792f753b35c575fe446760005d44e0be13ba8935a",
"02" + "b530da16bbff1428c33020e87fc9e699" +
"cc9c753a63b8678ce647b7457397acef",
"7012bc411228495f25d666d55fdce3f10a93908b5f9b9b7baa6e7573603a7bda"
};
for(int i = 0; i < 1; i++) {
byte[] priv = HEX.decode(ckdTestVectors[3 * i]);
byte[] pub = HEX.decode(ckdTestVectors[3 * i + 1]);
byte[] chain = HEX.decode(ckdTestVectors[3 * i + 2]); // chain code
//////////////////////////////////////////////////////////////////////////
// Start with an extended PRIVATE key
DeterministicKey ekprv = HDKeyDerivation.createMasterPrivKeyFromBytes(priv, chain);
// Create two accounts
DeterministicKey ekprv_0 = HDKeyDerivation.deriveChildKey(ekprv, 0);
DeterministicKey ekprv_1 = HDKeyDerivation.deriveChildKey(ekprv, 1);
// Create internal and external chain on Account 0
DeterministicKey ekprv_0_EX = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_EXTERNAL);
DeterministicKey ekprv_0_IN = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_INTERNAL);
// Create three addresses on external chain
DeterministicKey ekprv_0_EX_0 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 0);
DeterministicKey ekprv_0_EX_1 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 1);
DeterministicKey ekprv_0_EX_2 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 2);
// Create three addresses on internal chain
DeterministicKey ekprv_0_IN_0 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 0);
DeterministicKey ekprv_0_IN_1 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 1);
DeterministicKey ekprv_0_IN_2 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 2);
// Now add a few more addresses with very large indices
DeterministicKey ekprv_1_IN = HDKeyDerivation.deriveChildKey(ekprv_1, HDW_CHAIN_INTERNAL);
DeterministicKey ekprv_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4095);
// ExtendedHierarchicKey ekprv_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4294967295L);
//////////////////////////////////////////////////////////////////////////
// Repeat the above with PUBLIC key
DeterministicKey ekpub = HDKeyDerivation.createMasterPubKeyFromBytes(HDUtils.toCompressed(pub), chain);
// Create two accounts
DeterministicKey ekpub_0 = HDKeyDerivation.deriveChildKey(ekpub, 0);
DeterministicKey ekpub_1 = HDKeyDerivation.deriveChildKey(ekpub, 1);
// Create internal and external chain on Account 0
DeterministicKey ekpub_0_EX = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_EXTERNAL);
DeterministicKey ekpub_0_IN = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_INTERNAL);
// Create three addresses on external chain
DeterministicKey ekpub_0_EX_0 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 0);
DeterministicKey ekpub_0_EX_1 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 1);
DeterministicKey ekpub_0_EX_2 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 2);
// Create three addresses on internal chain
DeterministicKey ekpub_0_IN_0 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 0);
DeterministicKey ekpub_0_IN_1 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 1);
DeterministicKey ekpub_0_IN_2 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 2);
// Now add a few more addresses with very large indices
DeterministicKey ekpub_1_IN = HDKeyDerivation.deriveChildKey(ekpub_1, HDW_CHAIN_INTERNAL);
DeterministicKey ekpub_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4095);
// ExtendedHierarchicKey ekpub_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4294967295L);
assertEquals(hexEncodePub(ekprv.getPubOnly()), hexEncodePub(ekpub));
assertEquals(hexEncodePub(ekprv_0.getPubOnly()), hexEncodePub(ekpub_0));
assertEquals(hexEncodePub(ekprv_1.getPubOnly()), hexEncodePub(ekpub_1));
assertEquals(hexEncodePub(ekprv_0_IN.getPubOnly()), hexEncodePub(ekpub_0_IN));
assertEquals(hexEncodePub(ekprv_0_IN_0.getPubOnly()), hexEncodePub(ekpub_0_IN_0));
assertEquals(hexEncodePub(ekprv_0_IN_1.getPubOnly()), hexEncodePub(ekpub_0_IN_1));
assertEquals(hexEncodePub(ekprv_0_IN_2.getPubOnly()), hexEncodePub(ekpub_0_IN_2));
assertEquals(hexEncodePub(ekprv_0_EX_0.getPubOnly()), hexEncodePub(ekpub_0_EX_0));
assertEquals(hexEncodePub(ekprv_0_EX_1.getPubOnly()), hexEncodePub(ekpub_0_EX_1));
assertEquals(hexEncodePub(ekprv_0_EX_2.getPubOnly()), hexEncodePub(ekpub_0_EX_2));
assertEquals(hexEncodePub(ekprv_1_IN.getPubOnly()), hexEncodePub(ekpub_1_IN));
assertEquals(hexEncodePub(ekprv_1_IN_4095.getPubOnly()), hexEncodePub(ekpub_1_IN_4095));
//assertEquals(hexEncodePub(ekprv_1_IN_4bil.getPubOnly()), hexEncodePub(ekpub_1_IN_4bil));
}
}
@Test
public void inverseEqualsNormal() throws Exception {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("Wired / Aug 13th 2014 / Snowden: I Left the NSA Clues, But They Couldn't Find Them".getBytes());
HDKeyDerivation.RawKeyBytes key2 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.getPubOnly(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.NORMAL);
HDKeyDerivation.RawKeyBytes key3 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.getPubOnly(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.WITH_INVERSION);
assertArrayEquals(key2.keyBytes, key3.keyBytes);
assertArrayEquals(key2.chainCode, key3.chainCode);
}
@Test
public void encryptedDerivation() throws Exception {
// Check that encrypting a parent key in the heirarchy and then deriving from it yields a DeterministicKey
// with no private key component, and that the private key bytes are derived on demand.
KeyCrypter scrypter = new KeyCrypterScrypt();
KeyParameter aesKey = scrypter.deriveKey("we never went to the moon");
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("it was all a hoax".getBytes());
DeterministicKey encryptedKey1 = key1.encrypt(scrypter, aesKey, null);
DeterministicKey decryptedKey1 = encryptedKey1.decrypt(aesKey);
assertEquals(key1, decryptedKey1);
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO);
DeterministicKey derivedKey2 = HDKeyDerivation.deriveChildKey(encryptedKey1, ChildNumber.ZERO);
assertTrue(derivedKey2.isEncrypted()); // parent is encrypted.
DeterministicKey decryptedKey2 = derivedKey2.decrypt(aesKey);
assertFalse(decryptedKey2.isEncrypted());
assertEquals(key2, decryptedKey2);
Sha256Hash hash = Sha256Hash.create("the mainstream media won't cover it. why is that?".getBytes());
try {
derivedKey2.sign(hash);
fail();
} catch (ECKey.KeyIsEncryptedException e) {
// Ignored.
}
ECKey.ECDSASignature signature = derivedKey2.sign(hash, aesKey);
assertTrue(derivedKey2.verify(hash, signature));
}
@Test
public void pubOnlyDerivation() throws Exception {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED);
DeterministicKey key3 = HDKeyDerivation.deriveChildKey(key2, ChildNumber.ZERO);
DeterministicKey pubkey3 = HDKeyDerivation.deriveChildKey(key2.getPubOnly(), ChildNumber.ZERO);
assertEquals(key3.getPubKeyPoint(), pubkey3.getPubKeyPoint());
}
@Test
public void testSerializationMainAndTestNetworks() {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
NetworkParameters params = MainNetParams.get();
String pub58 = key1.serializePubB58(params);
String priv58 = key1.serializePrivB58(params);
assertEquals("xpub661MyMwAqRbcF7mq7Aejj5xZNzFfgi3ABamE9FedDHVmViSzSxYTgAQGcATDo2J821q7Y9EAagjg5EP3L7uBZk11PxZU3hikL59dexfLkz3", pub58);
assertEquals("xprv9s21ZrQH143K2dhN197jMx1ppxRBHFKJpMqdLsF1ewxncv7quRED8N5nksxphju3W7naj1arF56L5PUEWfuSk8h73Sb2uh7bSwyXNrjzhAZ", priv58);
params = TestNet3Params.get();
pub58 = key1.serializePubB58(params);
priv58 = key1.serializePrivB58(params);
assertEquals("tpubD6NzVbkrYhZ4WuxgZMdpw1Hvi7MKg6YDjDMXVohmZCFfF17hXBPYpc56rCY1KXFMovN29ik37nZimQseiykRTBTJTZJmjENyv2k3R12BJ1M", pub58);
assertEquals("tprv8ZgxMBicQKsPdSvtfhyEXbdp95qPWmMK9ukkDHfU8vTGQWrvtnZxe7TEg48Ui7HMsZKMj7CcQRg8YF1ydtFPZBxha5oLa3qeN3iwpYhHPVZ", priv58);
}
@Test
public void serializeToTextAndBytes() {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED);
// Creation time can't survive the xpub serialization format unfortunately.
key1.setCreationTimeSeconds(0);
NetworkParameters params = MainNetParams.get();
{
final String pub58 = key1.serializePubB58(params);
final String priv58 = key1.serializePrivB58(params);
final byte[] pub = key1.serializePublic(params);
final byte[] priv = key1.serializePrivate(params);
assertEquals("xpub661MyMwAqRbcF7mq7Aejj5xZNzFfgi3ABamE9FedDHVmViSzSxYTgAQGcATDo2J821q7Y9EAagjg5EP3L7uBZk11PxZU3hikL59dexfLkz3", pub58);
assertEquals("xprv9s21ZrQH143K2dhN197jMx1ppxRBHFKJpMqdLsF1ewxncv7quRED8N5nksxphju3W7naj1arF56L5PUEWfuSk8h73Sb2uh7bSwyXNrjzhAZ", priv58);
assertArrayEquals(new byte[]{4, -120, -78, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, -68, 93, -104, -97, 31, -105, -18, 109, 112, 104, 45, -77, -77, 18, 85, -29, -120, 86, -113, 26, 48, -18, -79, -110, -6, -27, 87, 86, 24, 124, 99, 3, 96, -33, -14, 67, -19, -47, 16, 76, -49, -11, -30, -123, 7, 56, 101, 91, 74, 125, -127, 61, 42, -103, 90, -93, 66, -36, 2, -126, -107, 30, 24, -111}, pub);
assertArrayEquals(new byte[]{4, -120, -83, -28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, -68, 93, -104, -97, 31, -105, -18, 109, 112, 104, 45, -77, -77, 18, 85, -29, -120, 86, -113, 26, 48, -18, -79, -110, -6, -27, 87, 86, 24, 124, 99, 0, -96, -75, 47, 90, -49, 92, -74, 92, -128, -125, 23, 38, -10, 97, -66, -19, 50, -112, 30, -111, -57, -124, 118, -86, 126, -35, -4, -51, 19, 109, 67, 116}, priv);
assertEquals(DeterministicKey.deserializeB58(null, priv58, params), key1);
assertEquals(DeterministicKey.deserializeB58(priv58, params), key1);
assertEquals(DeterministicKey.deserializeB58(null, pub58, params).getPubKeyPoint(), key1.getPubKeyPoint());
assertEquals(DeterministicKey.deserializeB58(pub58, params).getPubKeyPoint(), key1.getPubKeyPoint());
assertEquals(DeterministicKey.deserialize(params, priv, null), key1);
assertEquals(DeterministicKey.deserialize(params, priv), key1);
assertEquals(DeterministicKey.deserialize(params, pub, null).getPubKeyPoint(), key1.getPubKeyPoint());
assertEquals(DeterministicKey.deserialize(params, pub).getPubKeyPoint(), key1.getPubKeyPoint());
}
{
final String pub58 = key2.serializePubB58(params);
final String priv58 = key2.serializePrivB58(params);
final byte[] pub = key2.serializePublic(params);
final byte[] priv = key2.serializePrivate(params);
assertEquals(DeterministicKey.deserializeB58(key1, priv58, params), key2);
assertEquals(DeterministicKey.deserializeB58(key1, pub58, params).getPubKeyPoint(), key2.getPubKeyPoint());
assertEquals(DeterministicKey.deserialize(params, priv, key1), key2);
assertEquals(DeterministicKey.deserialize(params, pub, key1).getPubKeyPoint(), key2.getPubKeyPoint());
}
}
@Test
public void parentlessDeserialization() {
NetworkParameters params = UnitTestParams.get();
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED);
DeterministicKey key3 = HDKeyDerivation.deriveChildKey(key2, ChildNumber.ZERO_HARDENED);
DeterministicKey key4 = HDKeyDerivation.deriveChildKey(key3, ChildNumber.ZERO_HARDENED);
assertEquals(key4.getPath().size(), 3);
assertEquals(DeterministicKey.deserialize(params, key4.serializePrivate(params), key3).getPath().size(), 3);
assertEquals(DeterministicKey.deserialize(params, key4.serializePrivate(params), null).getPath().size(), 1);
assertEquals(DeterministicKey.deserialize(params, key4.serializePrivate(params)).getPath().size(), 1);
}
private static String hexEncodePub(DeterministicKey pubKey) {
return HEX.encode(pubKey.getPubKey());
}
}

View File

@ -1,184 +0,0 @@
/*
* Copyright 2013 Matija Mazi
* 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 com.dogecoin.dogecoinj.crypto;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Test;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import java.util.List;
public class HDUtilsTest {
@Test
public void testHmac() throws Exception {
String tv[] = {
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b" +
"0b0b0b0b",
"4869205468657265",
"87aa7cdea5ef619d4ff0b4241a1d6cb0" +
"2379f4e2ce4ec2787ad0b30545e17cde" +
"daa833b7d6b8a702038b274eaea3f4e4" +
"be9d914eeb61f1702e696c203a126854",
"4a656665",
"7768617420646f2079612077616e7420" +
"666f72206e6f7468696e673f",
"164b7a7bfcf819e2e395fbe73b56e0a3" +
"87bd64222e831fd610270cd7ea250554" +
"9758bf75c05a994a6d034f65f8f0e6fd" +
"caeab1a34d4a6b4b636e070a38bce737",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaa",
"dddddddddddddddddddddddddddddddd" +
"dddddddddddddddddddddddddddddddd" +
"dddddddddddddddddddddddddddddddd" +
"dddd",
"fa73b0089d56a284efb0f0756c890be9" +
"b1b5dbdd8ee81a3655f83e33b2279d39" +
"bf3e848279a722c806b485a47e67c807" +
"b946a337bee8942674278859e13292fb",
"0102030405060708090a0b0c0d0e0f10" +
"111213141516171819",
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
"cdcd",
"b0ba465637458c6990e5a8c5f61d4af7" +
"e576d97ff94b872de76f8050361ee3db" +
"a91ca5c11aa25eb4d679275cc5788063" +
"a5f19741120c4f2de2adebeb10a298dd",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaa",
"54657374205573696e67204c61726765" +
"72205468616e20426c6f636b2d53697a" +
"65204b6579202d2048617368204b6579" +
"204669727374",
"80b24263c7c1a3ebb71493c1dd7be8b4" +
"9b46d1f41b4aeec1121b013783f8f352" +
"6b56d037e05f2598bd0fd2215d6a1e52" +
"95e64f73f63f0aec8b915a985d786598",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaa",
"54686973206973206120746573742075" +
"73696e672061206c6172676572207468" +
"616e20626c6f636b2d73697a65206b65" +
"7920616e642061206c61726765722074" +
"68616e20626c6f636b2d73697a652064" +
"6174612e20546865206b6579206e6565" +
"647320746f2062652068617368656420" +
"6265666f7265206265696e6720757365" +
"642062792074686520484d414320616c" +
"676f726974686d2e",
"e37b6a775dc87dbaa4dfa9f96e5e3ffd" +
"debd71f8867289865df5a32d20cdc944" +
"b6022cac3c4982b10d5eeb55c3e4de15" +
"134676fb6de0446065c97440fa8c6a58"
};
for (int i = 0; i < tv.length; i += 3) {
Assert.assertArrayEquals("Case " + i, getBytes(tv, i + 2), HDUtils.hmacSha512(getBytes(tv, i), getBytes(tv, i + 1)));
}
}
private static byte[] getBytes(String[] hmacTestVectors, int i) {
return HEX.decode(hmacTestVectors[i]);
}
@Test
public void testLongToByteArray() throws Exception {
byte[] bytes = HDUtils.longTo4ByteArray(1026);
Assert.assertEquals("00000402", HEX.encode(bytes));
}
@Test
public void testFormatPath() {
Object tv[] = {
"M/44H/0H/0H/1/1",
ImmutableList.of(new ChildNumber(44, true), new ChildNumber(0, true), new ChildNumber(0, true),
new ChildNumber(1, false), new ChildNumber(1, false)),
"M/7H/3/3/1H",
ImmutableList.of(new ChildNumber(7, true), new ChildNumber(3, false), new ChildNumber(3, false),
new ChildNumber(1, true)),
"M/1H/2H/3H",
ImmutableList.of(new ChildNumber(1, true), new ChildNumber(2, true), new ChildNumber(3, true)),
"M/1/2/3",
ImmutableList.of(new ChildNumber(1, false), new ChildNumber(2, false), new ChildNumber(3, false))
};
for (int i = 0; i < tv.length; i += 2) {
String expectedStrPath = (String) tv[i];
List<ChildNumber> path = (List<ChildNumber>) tv[i+1];
String generatedStrPath = HDUtils.formatPath(path);
Assert.assertEquals(generatedStrPath, expectedStrPath);
}
}
@Test
public void testParsePath() {
Object tv[] = {
"M / 44H / 0H / 0H / 1 / 1",
ImmutableList.of(new ChildNumber(44, true), new ChildNumber(0, true), new ChildNumber(0, true),
new ChildNumber(1, false), new ChildNumber(1, false)),
"M/7H/3/3/1H/",
ImmutableList.of(new ChildNumber(7, true), new ChildNumber(3, false), new ChildNumber(3, false),
new ChildNumber(1, true)),
"1 H / 2 H / 3 H /",
ImmutableList.of(new ChildNumber(1, true), new ChildNumber(2, true), new ChildNumber(3, true)),
"1 / 2 / 3 /",
ImmutableList.of(new ChildNumber(1, false), new ChildNumber(2, false), new ChildNumber(3, false))
};
for (int i = 0; i < tv.length; i += 2) {
String strPath = (String) tv[i];
List<ChildNumber> expectedPath = (List<ChildNumber>) tv[i+1];
List<ChildNumber> path = HDUtils.parsePath(strPath);
Assert.assertEquals(path, expectedPath);
}
}
}

View File

@ -1,164 +0,0 @@
/**
* Copyright 2013 Jim Burton.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* 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.crypto;
import com.dogecoin.dogecoinj.core.Utils;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.google.protobuf.ByteString;
import com.dogecoin.dogecoinj.wallet.Protos;
import com.dogecoin.dogecoinj.wallet.Protos.ScryptParameters;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
import static org.junit.Assert.*;
public class KeyCrypterScryptTest {
private static final Logger log = LoggerFactory.getLogger(KeyCrypterScryptTest.class);
// Nonsense bytes for encryption test.
private static final byte[] TEST_BYTES1 = {0, -101, 2, 103, -4, 105, 6, 107, 8, -109, 10, 111, -12, 113, 14, -115, 16, 117, -18, 119, 20, 121, 22, 123, -24, 125, 26, 127, -28, 29, -30, 31};
private static final CharSequence PASSWORD1 = "aTestPassword";
private static final CharSequence PASSWORD2 = "0123456789";
private static final CharSequence WRONG_PASSWORD = "thisIsTheWrongPassword";
private ScryptParameters scryptParameters;
@Before
public void setUp() throws Exception {
byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH];
new SecureRandom().nextBytes(salt);
Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt));
scryptParameters = scryptParametersBuilder.build();
BriefLogFormatter.init();
}
@Test
public void testKeyCrypterGood1() throws KeyCrypterException {
KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters);
// Encrypt.
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(TEST_BYTES1, keyCrypter.deriveKey(PASSWORD1));
assertNotNull(encryptedPrivateKey);
// Decrypt.
byte[] reborn = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1));
log.debug("Original: " + Utils.HEX.encode(TEST_BYTES1));
log.debug("Reborn : " + Utils.HEX.encode(reborn));
assertEquals(Utils.HEX.encode(TEST_BYTES1), Utils.HEX.encode(reborn));
}
/**
* Test with random plain text strings and random passwords.
* UUIDs are used and hence will only cover hex characters (and the separator hyphen).
* @throws KeyCrypterException
* @throws UnsupportedEncodingException
*/
@Test
public void testKeyCrypterGood2() throws Exception {
KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters);
System.out.print("EncrypterDecrypterTest: Trying random UUIDs for plainText and passwords :");
int numberOfTests = 16;
for (int i = 0; i < numberOfTests; i++) {
// Create a UUID as the plaintext and use another for the password.
String plainText = UUID.randomUUID().toString();
CharSequence password = UUID.randomUUID().toString();
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(plainText.getBytes(), keyCrypter.deriveKey(password));
assertNotNull(encryptedPrivateKey);
byte[] reconstructedPlainBytes = keyCrypter.decrypt(encryptedPrivateKey,keyCrypter.deriveKey(password));
assertEquals(Utils.HEX.encode(plainText.getBytes()), Utils.HEX.encode(reconstructedPlainBytes));
System.out.print('.');
}
System.out.println(" Done.");
}
@Test
public void testKeyCrypterWrongPassword() throws KeyCrypterException {
KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters);
// create a longer encryption string
StringBuilder stringBuffer = new StringBuilder();
for (int i = 0; i < 100; i++) {
stringBuffer.append(i).append(" ").append("The quick brown fox");
}
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(stringBuffer.toString().getBytes(), keyCrypter.deriveKey(PASSWORD2));
assertNotNull(encryptedPrivateKey);
try {
keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(WRONG_PASSWORD));
// TODO: This test sometimes fails due to relying on padding.
fail("Decrypt with wrong password did not throw exception");
} catch (KeyCrypterException ede) {
assertTrue(ede.getMessage().contains("Could not decrypt"));
}
}
@Test
public void testEncryptDecryptBytes1() throws KeyCrypterException {
KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters);
// Encrypt bytes.
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(TEST_BYTES1, keyCrypter.deriveKey(PASSWORD1));
assertNotNull(encryptedPrivateKey);
log.debug("\nEncrypterDecrypterTest: cipherBytes = \nlength = " + encryptedPrivateKey.encryptedBytes.length + "\n---------------\n" + Utils.HEX.encode(encryptedPrivateKey.encryptedBytes) + "\n---------------\n");
byte[] rebornPlainBytes = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1));
log.debug("Original: " + Utils.HEX.encode(TEST_BYTES1));
log.debug("Reborn1 : " + Utils.HEX.encode(rebornPlainBytes));
assertEquals(Utils.HEX.encode(TEST_BYTES1), Utils.HEX.encode(rebornPlainBytes));
}
@Test
public void testEncryptDecryptBytes2() throws KeyCrypterException {
KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters);
// Encrypt random bytes of various lengths up to length 50.
Random random = new Random();
for (int i = 0; i < 50; i++) {
byte[] plainBytes = new byte[i];
random.nextBytes(plainBytes);
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(plainBytes, keyCrypter.deriveKey(PASSWORD1));
assertNotNull(encryptedPrivateKey);
//log.debug("\nEncrypterDecrypterTest: cipherBytes = \nlength = " + cipherBytes.length + "\n---------------\n" + Utils.HEX.encode(cipherBytes) + "\n---------------\n");
byte[] rebornPlainBytes = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1));
log.debug("Original: (" + i + ") " + Utils.HEX.encode(plainBytes));
log.debug("Reborn1 : (" + i + ") " + Utils.HEX.encode(rebornPlainBytes));
assertEquals(Utils.HEX.encode(plainBytes), Utils.HEX.encode(rebornPlainBytes));
}
}
}

View File

@ -1,218 +0,0 @@
/*
* Copyright 2013 Ken Sedgwick
* 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 com.dogecoin.dogecoinj.crypto;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static org.junit.Assert.assertEquals;
public class MnemonicCodeTest {
// These vectors are from https://github.com/trezor/python-mnemonic/blob/master/vectors.json
String vectors[] = {
"00000000000000000000000000000000",
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"legal winner thank year wave sausage worth useful legal winner thank yellow",
"2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",
"80808080808080808080808080808080",
"letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
"d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",
"ffffffffffffffffffffffffffffffff",
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
"ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",
"000000000000000000000000000000000000000000000000",
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
"035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
"f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",
"808080808080808080808080808080808080808080808080",
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
"107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",
"ffffffffffffffffffffffffffffffffffffffffffffffff",
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",
"0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",
"0000000000000000000000000000000000000000000000000000000000000000",
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
"bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
"bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
"8080808080808080808080808080808080808080808080808080808080808080",
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
"c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
"dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
"77c2b00716cec7213839159e404db50d",
"jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge",
"b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff",
"b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b",
"renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap",
"9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5",
"3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982",
"dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic",
"ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67",
"0460ef47585604c5660618db2e6a7e7f",
"afford alter spike radar gate glance object seek swamp infant panel yellow",
"65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4",
"72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f",
"indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left",
"3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba",
"2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416",
"clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste",
"fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449",
"eaebabb2383351fd31d703840b32e9e2",
"turtle front uncle idea crush write shrug there lottery flower risk shell",
"bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c404f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c",
"7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78",
"kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment",
"ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79",
"4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef",
"exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top",
"095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c",
"18ab19a9f54a9274f03e5209a2ac8a91",
"board flee heavy tunnel powder denial science ski answer betray cargo cat",
"6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8",
"18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4",
"board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief",
"f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9",
"15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419",
"beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut",
"b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
};
private MnemonicCode mc;
@Before
public void setup() throws IOException {
mc = new MnemonicCode();
}
@Test
public void testVectors() throws Exception {
for (int ii = 0; ii < vectors.length; ii += 3) {
String vecData = vectors[ii];
String vecCode = vectors[ii+1];
String vecSeed = vectors[ii+2];
List<String> code = mc.toMnemonic(HEX.decode(vecData));
byte[] seed = MnemonicCode.toSeed(code, "TREZOR");
byte[] entropy = mc.toEntropy(split(vecCode));
assertEquals(vecData, HEX.encode(entropy));
assertEquals(vecCode, Joiner.on(' ').join(code));
assertEquals(vecSeed, HEX.encode(seed));
}
}
@Test(expected = MnemonicException.MnemonicLengthException.class)
public void testBadEntropyLength() throws Exception {
byte[] entropy = HEX.decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f");
mc.toMnemonic(entropy);
}
@Test(expected = MnemonicException.MnemonicLengthException.class)
public void testBadLength() throws Exception {
List<String> words = split("risk tiger venture dinner age assume float denial penalty hello");
mc.check(words);
}
@Test(expected = MnemonicException.MnemonicWordException.class)
public void testBadWord() throws Exception {
List<String> words = split("risk tiger venture dinner xyzzy assume float denial penalty hello game wing");
mc.check(words);
}
@Test(expected = MnemonicException.MnemonicChecksumException.class)
public void testBadChecksum() throws Exception {
List<String> words = split("bless cloud wheel regular tiny venue bird web grief security dignity zoo");
mc.check(words);
}
@Test(expected = MnemonicException.MnemonicLengthException.class)
public void testEmptyMnemonic() throws Exception {
List<String> words = Lists.newArrayList();
mc.check(words);
}
@Test(expected = MnemonicException.MnemonicLengthException.class)
public void testEmptyEntropy() throws Exception {
byte[] entropy = new byte[]{};
mc.toMnemonic(entropy);
}
static public List<String> split(String words) {
return new ArrayList<String>(Arrays.asList(words.split("\\s+")));
}
}

View File

@ -1,40 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.crypto;
import org.junit.Test;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import static org.junit.Assert.assertEquals;
public class X509UtilsTest {
@Test
public void testDisplayName() throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate clientCert = (X509Certificate) cf.generateCertificate(getClass().getResourceAsStream(
"startssl-client.crt"));
assertEquals("Andreas Schildbach", X509Utils.getDisplayNameFromCertificate(clientCert, false));
X509Certificate comodoCert = (X509Certificate) cf.generateCertificate(getClass().getResourceAsStream(
"comodo-smime.crt"));
assertEquals("comodo.com@schildbach.de", X509Utils.getDisplayNameFromCertificate(comodoCert, true));
}
}

View File

@ -1,662 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.net;
import com.dogecoin.dogecoinj.core.Utils;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import javax.net.SocketFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@RunWith(value = Parameterized.class)
public class NetworkAbstractionTests {
private static final int CLIENT_MAJOR_VERSION = 1;
private AtomicBoolean fail;
private final int clientType;
private final ClientConnectionManager channels;
@Parameterized.Parameters
public static Collection<Integer[]> parameters() {
return Arrays.asList(new Integer[]{0}, new Integer[]{1}, new Integer[]{2}, new Integer[]{3});
}
public NetworkAbstractionTests(Integer clientType) throws Exception {
this.clientType = clientType;
if (clientType == 0) {
channels = new NioClientManager();
channels.startAsync();
} else if (clientType == 1) {
channels = new BlockingClientManager();
channels.startAsync();
} else
channels = null;
}
private MessageWriteTarget openConnection(SocketAddress addr, ProtobufParser<Protos.TwoWayChannelMessage> parser) throws Exception {
if (clientType == 0 || clientType == 1) {
channels.openConnection(addr, parser);
if (parser.writeTarget.get() == null)
Thread.sleep(100);
return (MessageWriteTarget) parser.writeTarget.get();
} else if (clientType == 2)
return new NioClient(addr, parser, 100);
else if (clientType == 3)
return new BlockingClient(addr, parser, 100, SocketFactory.getDefault(), null);
else
throw new RuntimeException();
}
@Before
public void setUp() {
fail = new AtomicBoolean(false);
}
@After
public void checkFail() {
assertFalse(fail.get());
}
@Test
public void testNullGetNewParser() throws Exception {
final SettableFuture<Void> client1ConnectionOpened = SettableFuture.create();
final SettableFuture<Void> client1Disconnected = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client2MessageReceived = SettableFuture.create();
final SettableFuture<Void> serverConnectionOpen = SettableFuture.create();
final SettableFuture<Void> client2ConnectionOpened = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed = SettableFuture.create();
final SettableFuture<Void> client2Disconnected = SettableFuture.create();
NioServer server = new NioServer(new StreamParserFactory() {
boolean finishedFirst = false;
@Override
public ProtobufParser<TwoWayChannelMessage> getNewParser(InetAddress inetAddress, int port) {
if (!finishedFirst) {
finishedFirst = true;
return null;
}
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
}
}, new InetSocketAddress("localhost", 4243));
server.startAsync();
server.awaitRunning();
ProtobufParser<Protos.TwoWayChannelMessage> clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client1ConnectionOpened.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client1Disconnected.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
openConnection(new InetSocketAddress("localhost", 4243), clientHandler);
client1ConnectionOpened.get();
client1Disconnected.get();
clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
if (client2MessageReceived.isDone())
fail.set(true);
client2MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client2ConnectionOpened.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client2Disconnected.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
MessageWriteTarget client = openConnection(new InetSocketAddress("localhost", 4243), clientHandler);
serverConnectionOpen.get();
client2ConnectionOpened.get();
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build();
clientHandler.write(msg);
assertEquals(msg, client2MessageReceived.get());
client.closeConnection();
serverConnectionClosed.get();
client2Disconnected.get();
server.stopAsync().awaitTerminated();
}
@Test
public void basicClientServerTest() throws Exception {
// Tests creating a basic server, opening a client connection and sending a few messages
final SettableFuture<Void> serverConnectionOpen = SettableFuture.create();
final SettableFuture<Void> clientConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed = SettableFuture.create();
final SettableFuture<Void> clientConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage1Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage2Received = SettableFuture.create();
NioServer server = new NioServer(new StreamParserFactory() {
@Override
public ProtobufParser<TwoWayChannelMessage> getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
handler.write(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
}
}, new InetSocketAddress("localhost", 4243));
server.startAsync();
server.awaitRunning();
ProtobufParser<Protos.TwoWayChannelMessage> clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
if (clientMessage1Received.isDone())
clientMessage2Received.set(msg);
else
clientMessage1Received.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
MessageWriteTarget client = openConnection(new InetSocketAddress("localhost", 4243), clientHandler);
clientConnectionOpen.get();
serverConnectionOpen.get();
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build();
clientHandler.write(msg);
assertEquals(msg, clientMessage1Received.get());
assertEquals(msg, clientMessage2Received.get());
client.closeConnection();
serverConnectionClosed.get();
clientConnectionClosed.get();
server.stopAsync();
server.awaitTerminated();
assertFalse(server.isRunning());
}
@Test
public void basicTimeoutTest() throws Exception {
// Tests various timeout scenarios
final SettableFuture<Void> serverConnection1Open = SettableFuture.create();
final SettableFuture<Void> clientConnection1Open = SettableFuture.create();
final SettableFuture<Void> serverConnection1Closed = SettableFuture.create();
final SettableFuture<Void> clientConnection1Closed = SettableFuture.create();
final SettableFuture<Void> serverConnection2Open = SettableFuture.create();
final SettableFuture<Void> clientConnection2Open = SettableFuture.create();
final SettableFuture<Void> serverConnection2Closed = SettableFuture.create();
final SettableFuture<Void> clientConnection2Closed = SettableFuture.create();
NioServer server = new NioServer(new StreamParserFactory() {
@Override
public ProtobufParser<Protos.TwoWayChannelMessage> getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public synchronized void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
if (serverConnection1Open.isDone()) {
handler.setSocketTimeout(0);
serverConnection2Open.set(null);
} else
serverConnection1Open.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
if (serverConnection1Closed.isDone()) {
serverConnection2Closed.set(null);
} else
serverConnection1Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 10);
}
}, new InetSocketAddress("localhost", 4243));
server.startAsync();
server.awaitRunning();
openConnection(new InetSocketAddress("localhost", 4243), new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnection1Open.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnection1Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0));
clientConnection1Open.get();
serverConnection1Open.get();
long closeDelayStart = System.currentTimeMillis();
clientConnection1Closed.get();
serverConnection1Closed.get();
long closeDelayFinish = System.currentTimeMillis();
ProtobufParser<Protos.TwoWayChannelMessage> client2Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnection2Open.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnection2Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
openConnection(new InetSocketAddress("localhost", 4243), client2Handler);
clientConnection2Open.get();
serverConnection2Open.get();
Thread.sleep((closeDelayFinish - closeDelayStart) * 10);
assertFalse(clientConnection2Closed.isDone() || serverConnection2Closed.isDone());
client2Handler.setSocketTimeout(10);
clientConnection2Closed.get();
serverConnection2Closed.get();
server.stopAsync();
server.awaitTerminated();
}
@Test
public void largeDataTest() throws Exception {
/** Test various large-data handling, essentially testing {@link ProtobufParser#receiveBytes(java.nio.ByteBuffer)} */
final SettableFuture<Void> serverConnectionOpen = SettableFuture.create();
final SettableFuture<Void> clientConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed = SettableFuture.create();
final SettableFuture<Void> clientConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage1Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage2Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage3Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage4Received = SettableFuture.create();
NioServer server = new NioServer(new StreamParserFactory() {
@Override
public ProtobufParser<Protos.TwoWayChannelMessage> getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
serverConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 0x10000, 0);
}
}, new InetSocketAddress("localhost", 4243));
server.startAsync();
server.awaitRunning();
ProtobufParser<Protos.TwoWayChannelMessage> clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
if (clientMessage1Received.isDone()) {
if (clientMessage2Received.isDone()) {
if (clientMessage3Received.isDone()) {
if (clientMessage4Received.isDone())
fail.set(true);
clientMessage4Received.set(msg);
} else
clientMessage3Received.set(msg);
} else
clientMessage2Received.set(msg);
} else
clientMessage1Received.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
clientConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 0x10000, 0);
MessageWriteTarget client = openConnection(new InetSocketAddress("localhost", 4243), clientHandler);
clientConnectionOpen.get();
serverConnectionOpen.get();
// Large message that is larger than buffer and equal to maximum message size
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(CLIENT_MAJOR_VERSION)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[0x10000 - 12])))
.build();
// Small message that fits in the buffer
Protos.TwoWayChannelMessage msg2 = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(CLIENT_MAJOR_VERSION)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[1])))
.build();
// Break up the message into chunks to simulate packet network (with strange MTUs...)
byte[] messageBytes = msg.toByteArray();
byte[] messageLength = new byte[4];
Utils.uint32ToByteArrayBE(messageBytes.length, messageLength, 0);
client.writeBytes(new byte[]{messageLength[0], messageLength[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageLength[2], messageLength[3]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes[0], messageBytes[1]});
Thread.sleep(10);
client.writeBytes(Arrays.copyOfRange(messageBytes, 2, messageBytes.length - 1));
Thread.sleep(10);
// Now send the end of msg + msg2 + msg3 all at once
byte[] messageBytes2 = msg2.toByteArray();
byte[] messageLength2 = new byte[4];
Utils.uint32ToByteArrayBE(messageBytes2.length, messageLength2, 0);
byte[] sendBytes = Arrays.copyOf(new byte[] {messageBytes[messageBytes.length-1]}, 1 + messageBytes2.length*2 + messageLength2.length*2);
System.arraycopy(messageLength2, 0, sendBytes, 1, 4);
System.arraycopy(messageBytes2, 0, sendBytes, 5, messageBytes2.length);
System.arraycopy(messageLength2, 0, sendBytes, 5 + messageBytes2.length, 4);
System.arraycopy(messageBytes2, 0, sendBytes, 9 + messageBytes2.length, messageBytes2.length);
client.writeBytes(sendBytes);
assertEquals(msg, clientMessage1Received.get());
assertEquals(msg2, clientMessage2Received.get());
assertEquals(msg2, clientMessage3Received.get());
// Now resent msg2 in chunks, by itself
Utils.uint32ToByteArrayBE(messageBytes2.length, messageLength2, 0);
client.writeBytes(new byte[]{messageLength2[0], messageLength2[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageLength2[2], messageLength2[3]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes2[0], messageBytes2[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes2[2], messageBytes2[3]});
Thread.sleep(10);
client.writeBytes(Arrays.copyOfRange(messageBytes2, 4, messageBytes2.length));
assertEquals(msg2, clientMessage4Received.get());
Protos.TwoWayChannelMessage msg5 = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(CLIENT_MAJOR_VERSION)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[0x10000 - 11])))
.build();
try {
clientHandler.write(msg5);
} catch (IllegalStateException e) {}
// Override max size and make sure the server drops our connection
byte[] messageLength5 = new byte[4];
Utils.uint32ToByteArrayBE(msg5.toByteArray().length, messageLength5, 0);
client.writeBytes(messageLength5);
serverConnectionClosed.get();
clientConnectionClosed.get();
server.stopAsync();
server.awaitTerminated();
}
@Test
public void testConnectionEventHandlers() throws Exception {
final SettableFuture<Void> serverConnection1Open = SettableFuture.create();
final SettableFuture<Void> serverConnection2Open = SettableFuture.create();
final SettableFuture<Void> serverConnection3Open = SettableFuture.create();
final SettableFuture<Void> client1ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> client2ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> client3ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed1 = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed2 = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed3 = SettableFuture.create();
final SettableFuture<Void> client1ConnectionClosed = SettableFuture.create();
final SettableFuture<Void> client2ConnectionClosed = SettableFuture.create();
final SettableFuture<Void> client3ConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client1MessageReceived = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client2MessageReceived = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client3MessageReceived = SettableFuture.create();
NioServer server = new NioServer(new StreamParserFactory() {
@Override
public ProtobufParser<Protos.TwoWayChannelMessage> getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
}
@Override
public synchronized void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
if (serverConnection1Open.isDone()) {
if (serverConnection2Open.isDone())
serverConnection3Open.set(null);
else
serverConnection2Open.set(null);
} else
serverConnection1Open.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
if (serverConnectionClosed1.isDone()) {
if (serverConnectionClosed2.isDone()) {
checkState(!serverConnectionClosed3.isDone());
serverConnectionClosed3.set(null);
} else
serverConnectionClosed2.set(null);
} else
serverConnectionClosed1.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
}
}, new InetSocketAddress("localhost", 4243));
server.startAsync();
server.awaitRunning();
ProtobufParser<Protos.TwoWayChannelMessage> client1Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
client1MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client1ConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client1ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
MessageWriteTarget client1 = openConnection(new InetSocketAddress("localhost", 4243), client1Handler);
client1ConnectionOpen.get();
serverConnection1Open.get();
ProtobufParser<Protos.TwoWayChannelMessage> client2Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
client2MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client2ConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client2ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
openConnection(new InetSocketAddress("localhost", 4243), client2Handler);
client2ConnectionOpen.get();
serverConnection2Open.get();
ProtobufParser<Protos.TwoWayChannelMessage> client3Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
client3MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
client3ConnectionOpen.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) {
checkState(!client3ConnectionClosed.isDone());
client3ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
NioClient client3 = new NioClient(new InetSocketAddress("localhost", 4243), client3Handler, 0);
client3ConnectionOpen.get();
serverConnection3Open.get();
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build();
client1Handler.write(msg);
assertEquals(msg, client1MessageReceived.get());
Protos.TwoWayChannelMessage msg2 = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.INITIATE).build();
client2Handler.write(msg2);
assertEquals(msg2, client2MessageReceived.get());
client1.closeConnection();
serverConnectionClosed1.get();
client1ConnectionClosed.get();
Protos.TwoWayChannelMessage msg3 = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CLOSE).build();
client3Handler.write(msg3);
assertEquals(msg3, client3MessageReceived.get());
// Try to create a race condition by triggering handlerThread closing and client3 closing at the same time
// This often triggers ClosedByInterruptException in handleKey
server.stopAsync();
server.selector.wakeup();
client3.closeConnection();
client3ConnectionClosed.get();
serverConnectionClosed3.get();
server.stopAsync();
server.awaitTerminated();
client2ConnectionClosed.get();
serverConnectionClosed2.get();
server.stopAsync();
server.awaitTerminated();
}
}

View File

@ -1,50 +0,0 @@
/**
* Copyright 2011 Micheal Swiggs
*
* 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.net.discovery;
import com.dogecoin.dogecoinj.params.MainNetParams;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
public class SeedPeersTest {
@Test
public void getPeer_one() throws Exception{
SeedPeers seedPeers = new SeedPeers(MainNetParams.get());
assertThat(seedPeers.getPeer(), notNullValue());
}
@Test
public void getPeer_all() throws Exception{
SeedPeers seedPeers = new SeedPeers(MainNetParams.get());
for(int i = 0; i < SeedPeers.seedAddrs.length; ++i){
assertThat("Failed on index: "+i, seedPeers.getPeer(), notNullValue());
}
assertThat(seedPeers.getPeer(), equalTo(null));
}
@Test
public void getPeers_length() throws Exception{
SeedPeers seedPeers = new SeedPeers(MainNetParams.get());
InetSocketAddress[] addresses = seedPeers.getPeers(0, TimeUnit.SECONDS);
assertThat(addresses.length, equalTo(SeedPeers.seedAddrs.length));
}
}

View File

@ -1,781 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.protocols.channels;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.store.WalletProtobufSerializer;
import com.dogecoin.dogecoinj.testing.TestWithWallet;
import com.dogecoin.dogecoinj.utils.Threading;
import com.dogecoin.dogecoinj.wallet.WalletFiles;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.protocols.channels.PaymentChannelCloseException.CloseReason;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeBlock;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType;
import static org.junit.Assert.*;
public class ChannelConnectionTest extends TestWithWallet {
private static final int CLIENT_MAJOR_VERSION = 1;
private Wallet serverWallet;
private BlockChain serverChain;
private AtomicBoolean fail;
private BlockingQueue<Transaction> broadcasts;
private TransactionBroadcaster mockBroadcaster;
private Semaphore broadcastTxPause;
private static final TransactionBroadcaster failBroadcaster = new TransactionBroadcaster() {
@Override
public ListenableFuture<Transaction> broadcastTransaction(Transaction tx) {
fail();
return null;
}
};
@Override
@Before
public void setUp() throws Exception {
super.setUp();
Utils.setMockClock(); // Use mock clock
sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.addExtension(new StoredPaymentChannelClientStates(wallet, failBroadcaster));
serverWallet = new Wallet(params);
serverWallet.addExtension(new StoredPaymentChannelServerStates(serverWallet, failBroadcaster));
serverWallet.freshReceiveKey();
serverChain = new BlockChain(params, serverWallet, blockStore);
// Use an atomic boolean to indicate failure because fail()/assert*() dont work in network threads
fail = new AtomicBoolean(false);
// Set up a way to monitor broadcast transactions. When you expect a broadcast, you must release a permit
// to the broadcastTxPause semaphore so state can be queried in between.
broadcasts = new LinkedBlockingQueue<Transaction>();
broadcastTxPause = new Semaphore(0);
mockBroadcaster = new TransactionBroadcaster() {
@Override
public ListenableFuture<Transaction> broadcastTransaction(Transaction tx) {
broadcastTxPause.acquireUninterruptibly();
SettableFuture<Transaction> future = SettableFuture.create();
future.set(tx);
broadcasts.add(tx);
return future;
}
};
// Because there are no separate threads in the tests here (we call back into client/server in server/client
// handlers), we have lots of lock cycles. A normal user shouldn't have this issue as they are probably not both
// client+server running in the same thread.
Threading.warnOnLockCycles();
ECKey.FAKE_SIGNATURES = true;
}
@After
@Override
public void tearDown() throws Exception {
super.tearDown();
ECKey.FAKE_SIGNATURES = false;
}
@After
public void checkFail() {
assertFalse(fail.get());
Threading.throwOnLockCycles();
}
@Test
public void testSimpleChannel() throws Exception {
exectuteSimpleChannelTest(null);
}
@Test
public void testEncryptedClientWallet() throws Exception {
// Encrypt the client wallet
String mySecretPw = "MySecret";
wallet.encrypt(mySecretPw);
KeyParameter userKeySetup = wallet.getKeyCrypter().deriveKey(mySecretPw);
exectuteSimpleChannelTest(userKeySetup);
}
public void exectuteSimpleChannelTest(KeyParameter userKeySetup) throws Exception {
// Test with network code and without any issues. We'll broadcast two txns: multisig contract and settle transaction.
final SettableFuture<ListenableFuture<PaymentChannelServerState>> serverCloseFuture = SettableFuture.create();
final SettableFuture<Sha256Hash> channelOpenFuture = SettableFuture.create();
final BlockingQueue<ChannelTestUtils.UpdatePair> q = new LinkedBlockingQueue<ChannelTestUtils.UpdatePair>();
final PaymentChannelServerListener server = new PaymentChannelServerListener(mockBroadcaster, serverWallet, 30, COIN,
new PaymentChannelServerListener.HandlerFactory() {
@Nullable
@Override
public ServerConnectionEventHandler onNewConnection(SocketAddress clientAddress) {
return new ServerConnectionEventHandler() {
@Override
public void channelOpen(Sha256Hash channelId) {
channelOpenFuture.set(channelId);
}
@Override
public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, ByteString info) {
q.add(new ChannelTestUtils.UpdatePair(to, info));
return Futures.immediateFuture(info);
}
@Override
public void channelClosed(CloseReason reason) {
serverCloseFuture.set(null);
}
};
}
});
server.bindAndStart(4243);
PaymentChannelClientConnection client = new PaymentChannelClientConnection(
new InetSocketAddress("localhost", 4243), 30, wallet, myKey, COIN, "", PaymentChannelClient.DEFAULT_TIME_WINDOW, userKeySetup);
// Wait for the multi-sig tx to be transmitted.
broadcastTxPause.release();
Transaction broadcastMultiSig = broadcasts.take();
// Wait for the channel to finish opening.
client.getChannelOpenFuture().get();
assertEquals(broadcastMultiSig.getHash(), channelOpenFuture.get());
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, client.state().getValueSpent());
// Set up an autosave listener to make sure the server is saving the wallet after each payment increase.
final CountDownLatch latch = new CountDownLatch(3); // Expect 3 calls.
File tempFile = File.createTempFile("channel_connection_test", ".wallet");
tempFile.deleteOnExit();
serverWallet.autosaveToFile(tempFile, 0, TimeUnit.SECONDS, new WalletFiles.Listener() {
@Override
public void onBeforeAutoSave(File tempFile) {
latch.countDown();
}
@Override
public void onAfterAutoSave(File newlySavedFile) {
}
});
Thread.sleep(1250); // No timeouts once the channel is open
Coin amount = client.state().getValueSpent();
q.take().assertPair(amount, null);
for (String info : new String[] {null, "one", "two"} ) {
final ByteString bytes = (info==null) ? null :ByteString.copyFromUtf8(info);
final PaymentIncrementAck ack = client.incrementPayment(CENT, bytes, userKeySetup).get();
if (info != null) {
final ByteString ackInfo = ack.getInfo();
assertNotNull("Ack info is null", ackInfo);
assertEquals("Ack info differs ", info, ackInfo.toStringUtf8());
}
amount = amount.add(CENT);
q.take().assertPair(amount, bytes);
}
latch.await();
StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)serverWallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
StoredServerChannel storedServerChannel = channels.getChannel(broadcastMultiSig.getHash());
PaymentChannelServerState serverState = storedServerChannel.getOrCreateState(serverWallet, mockBroadcaster);
// Check that you can call settle multiple times with no exceptions.
client.settle();
client.settle();
broadcastTxPause.release();
Transaction settleTx = broadcasts.take();
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
if (!serverState.getBestValueToMe().equals(amount) || !serverState.getFeePaid().equals(Coin.ZERO))
fail();
assertTrue(channels.mapChannels.isEmpty());
// Send the settle TX to the client wallet.
sendMoneyToWallet(settleTx, AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertEquals(PaymentChannelClientState.State.CLOSED, client.state().getState());
server.close();
server.close();
// Now confirm the settle TX and see if the channel deletes itself from the wallet.
assertEquals(1, StoredPaymentChannelClientStates.getFromWallet(wallet).mapChannels.size());
wallet.notifyNewBestBlock(createFakeBlock(blockStore).storedBlock);
assertEquals(1, StoredPaymentChannelClientStates.getFromWallet(wallet).mapChannels.size());
wallet.notifyNewBestBlock(createFakeBlock(blockStore).storedBlock);
assertEquals(0, StoredPaymentChannelClientStates.getFromWallet(wallet).mapChannels.size());
}
@Test
public void testServerErrorHandling() throws Exception {
// Gives the server crap and checks proper error responses are sent.
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
// Make sure we get back a BAD_TRANSACTION if we send a bogus refund transaction.
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND);
server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.PROVIDE_REFUND)
.setProvideRefund(
Protos.ProvideRefund.newBuilder(msg.getProvideRefund())
.setMultisigKey(ByteString.EMPTY)
.setTx(ByteString.EMPTY)
).build());
final Protos.TwoWayChannelMessage errorMsg = pair.serverRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(Protos.Error.ErrorCode.BAD_TRANSACTION, errorMsg.getError().getCode());
// Make sure the server closes the socket on CLOSE
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
server = pair.server;
server.connectionOpen();
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.settle();
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLOSE));
assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, pair.serverRecorder.q.take());
// Make sure the server closes the socket on ERROR
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
server = pair.server;
server.connectionOpen();
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.ERROR)
.setError(Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.TIMEOUT))
.build());
assertEquals(CloseReason.REMOTE_SENT_ERROR, pair.serverRecorder.q.take());
}
@Test
public void testChannelResume() throws Exception {
// Tests various aspects of channel resuming.
Utils.setMockClock();
final Sha256Hash someServerId = Sha256Hash.create(new byte[]{});
// Open up a normal channel.
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
final Protos.TwoWayChannelMessage initiateMsg = pair.serverRecorder.checkNextMsg(MessageType.INITIATE);
Coin minPayment = Coin.valueOf(initiateMsg.getInitiate().getMinPayment());
client.receiveMessage(initiateMsg);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND));
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT));
broadcasts.take();
pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
Sha256Hash contractHash = (Sha256Hash) pair.serverRecorder.q.take();
pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
assertEquals(minPayment, client.state().getValueSpent());
// Send a bitcent.
Coin amount = minPayment.add(CENT);
client.incrementPayment(CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
assertEquals(amount, ((ChannelTestUtils.UpdatePair)pair.serverRecorder.q.take()).amount);
server.close();
server.connectionClosed();
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CLOSE));
client.connectionClosed();
assertFalse(client.connectionOpen);
// There is now an inactive open channel worth COIN-CENT + minPayment with id Sha256.create(new byte[] {})
StoredPaymentChannelClientStates clientStoredChannels =
(StoredPaymentChannelClientStates) wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
assertEquals(1, clientStoredChannels.mapChannels.size());
assertFalse(clientStoredChannels.mapChannels.values().iterator().next().active);
// Check that server-side won't attempt to reopen a nonexistent channel (it will tell the client to re-initiate
// instead).
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
pair.server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.CLIENT_VERSION)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setPreviousChannelContractHash(ByteString.copyFrom(Sha256Hash.create(new byte[]{0x03}).getBytes()))
.setMajor(CLIENT_MAJOR_VERSION).setMinor(42))
.build());
pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION);
pair.serverRecorder.checkNextMsg(MessageType.INITIATE);
// Now reopen/resume the channel after round-tripping the wallets.
wallet = roundTripClientWallet(wallet);
serverWallet = roundTripServerWallet(serverWallet);
clientStoredChannels =
(StoredPaymentChannelClientStates) wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
// Check the contract hash is sent on the wire correctly.
final Protos.TwoWayChannelMessage clientVersionMsg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
assertTrue(clientVersionMsg.getClientVersion().hasPreviousChannelContractHash());
assertEquals(contractHash, new Sha256Hash(clientVersionMsg.getClientVersion().getPreviousChannelContractHash().toByteArray()));
server.receiveMessage(clientVersionMsg);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
assertEquals(contractHash, pair.serverRecorder.q.take());
pair.clientRecorder.checkOpened();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
// Send another bitcent and check 2 were received in total.
client.incrementPayment(CENT);
amount = amount.add(CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
pair.serverRecorder.checkTotalPayment(amount);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK));
PaymentChannelClient openClient = client;
ChannelTestUtils.RecordingPair openPair = pair;
// Now open up a new client with the same id and make sure the server disconnects the previous client.
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
// Check that no prev contract hash is sent on the wire the client notices it's already in use by another
// client attached to the same wallet and refuses to resume.
{
Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
assertFalse(msg.getClientVersion().hasPreviousChannelContractHash());
}
// Make sure the server allows two simultaneous opens. It will close the first and allow resumption of the second.
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
// Swap out the clients version message for a custom one that tries to resume ...
pair.clientRecorder.getNextMsg();
server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.CLIENT_VERSION)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setPreviousChannelContractHash(ByteString.copyFrom(contractHash.getBytes()))
.setMajor(CLIENT_MAJOR_VERSION).setMinor(42))
.build());
// We get the usual resume sequence.
pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION);
pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN);
// Verify the previous one was closed.
openPair.serverRecorder.checkNextMsg(MessageType.CLOSE);
assertTrue(clientStoredChannels.getChannel(someServerId, contractHash).active);
// And finally close the first channel too.
openClient.connectionClosed();
assertFalse(clientStoredChannels.getChannel(someServerId, contractHash).active);
// Now roll the mock clock and recreate the client object so that it removes the channels and announces refunds.
assertEquals(86640, clientStoredChannels.getSecondsUntilExpiry(someServerId));
Utils.rollMockClock(60 * 60 * 24 + 60 * 5); // Client announces refund 5 minutes after expire time
StoredPaymentChannelClientStates newClientStates = new StoredPaymentChannelClientStates(wallet, mockBroadcaster);
newClientStates.deserializeWalletExtension(wallet, clientStoredChannels.serializeWalletExtension());
broadcastTxPause.release();
assertTrue(broadcasts.take().getOutput(0).getScriptPubKey().isSentToMultiSig());
broadcastTxPause.release();
assertEquals(TransactionConfidence.Source.SELF, broadcasts.take().getConfidence().getSource());
assertTrue(broadcasts.isEmpty());
assertTrue(newClientStates.mapChannels.isEmpty());
// Server also knows it's too late.
StoredPaymentChannelServerStates serverStoredChannels = new StoredPaymentChannelServerStates(serverWallet, mockBroadcaster);
Thread.sleep(2000); // TODO: Fix this stupid hack.
assertTrue(serverStoredChannels.mapChannels.isEmpty());
}
private static Wallet roundTripClientWallet(Wallet wallet) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new WalletProtobufSerializer().writeWallet(wallet, bos);
com.dogecoin.dogecoinj.wallet.Protos.Wallet proto = WalletProtobufSerializer.parseToProto(new ByteArrayInputStream(bos.toByteArray()));
StoredPaymentChannelClientStates state = new StoredPaymentChannelClientStates(null, failBroadcaster);
return new WalletProtobufSerializer().readWallet(wallet.getParams(), new WalletExtension[] { state }, proto);
}
private static Wallet roundTripServerWallet(Wallet wallet) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new WalletProtobufSerializer().writeWallet(wallet, bos);
StoredPaymentChannelServerStates state = new StoredPaymentChannelServerStates(null, failBroadcaster);
com.dogecoin.dogecoinj.wallet.Protos.Wallet proto = WalletProtobufSerializer.parseToProto(new ByteArrayInputStream(bos.toByteArray()));
return new WalletProtobufSerializer().readWallet(wallet.getParams(), new WalletExtension[] { state }, proto);
}
@Test
public void testBadResumeHash() throws InterruptedException {
// Check that server-side will reject incorrectly formatted hashes. If anything goes wrong with session resume,
// then the server will start the opening of a new channel automatically, so we expect to see INITIATE here.
ChannelTestUtils.RecordingPair srv =
ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
srv.server.connectionOpen();
srv.server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.CLIENT_VERSION)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[]{0x00, 0x01}))
.setMajor(CLIENT_MAJOR_VERSION).setMinor(42))
.build());
srv.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION);
srv.serverRecorder.checkNextMsg(MessageType.INITIATE);
assertTrue(srv.serverRecorder.q.isEmpty());
}
@Test
public void testClientUnknownVersion() throws Exception {
// Tests client rejects unknown version
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setServerVersion(Protos.ServerVersion.newBuilder().setMajor(-1))
.setType(MessageType.SERVER_VERSION).build());
pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(CloseReason.NO_ACCEPTABLE_VERSION, pair.clientRecorder.q.take());
// Double-check that we cant do anything that requires an open channel
try {
client.incrementPayment(Coin.SATOSHI);
fail();
} catch (IllegalStateException e) { }
}
@Test
public void testClientTimeWindowUnacceptable() throws Exception {
// Tests that clients reject too large time windows
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster, 100);
PaymentChannelServer server = pair.server;
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds() + 60 * 60 * 48)
.setMinAcceptedChannelSize(100)
.setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey()))
.setMinPayment(Transaction.MIN_NONDUST_OUTPUT.value))
.setType(MessageType.INITIATE).build());
pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(CloseReason.TIME_WINDOW_UNACCEPTABLE, pair.clientRecorder.q.take());
// Double-check that we cant do anything that requires an open channel
try {
client.incrementPayment(Coin.SATOSHI);
fail();
} catch (IllegalStateException e) { }
}
@Test
public void testValuesAreRespected() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds())
.setMinAcceptedChannelSize(COIN.add(SATOSHI).value)
.setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey()))
.setMinPayment(Transaction.MIN_NONDUST_OUTPUT.value))
.setType(MessageType.INITIATE).build());
pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE, pair.clientRecorder.q.take());
// Double-check that we cant do anything that requires an open channel
try {
client.incrementPayment(Coin.SATOSHI);
fail();
} catch (IllegalStateException e) { }
// Now check that if the server has a lower min size than what we are willing to spend, we do actually open
// a channel of that size.
sendMoneyToWallet(COIN.multiply(10), AbstractBlockChain.NewBlockType.BEST_CHAIN);
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
server = pair.server;
final Coin myValue = COIN.multiply(10);
client = new PaymentChannelClient(wallet, myKey, myValue, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds())
.setMinAcceptedChannelSize(COIN.add(SATOSHI).value)
.setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey()))
.setMinPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value))
.setType(MessageType.INITIATE).build());
final Protos.TwoWayChannelMessage provideRefund = pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND);
Transaction refund = new Transaction(params, provideRefund.getProvideRefund().getTx().toByteArray());
assertEquals(myValue, refund.getOutput(0).getValue());
}
@Test
public void testEmptyWallet() throws Exception {
Wallet emptyWallet = new Wallet(params);
emptyWallet.freshReceiveKey();
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
PaymentChannelClient client = new PaymentChannelClient(emptyWallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
try {
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds())
.setMinAcceptedChannelSize(CENT.value)
.setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey()))
.setMinPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value))
.setType(MessageType.INITIATE).build());
fail();
} catch (InsufficientMoneyException expected) {
// This should be thrown.
}
}
@Test
public void testClientRefusesNonCanonicalKey() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
Protos.TwoWayChannelMessage.Builder initiateMsg = Protos.TwoWayChannelMessage.newBuilder(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
ByteString brokenKey = initiateMsg.getInitiate().getMultisigKey();
brokenKey = ByteString.copyFrom(Arrays.copyOf(brokenKey.toByteArray(), brokenKey.size() + 1));
initiateMsg.getInitiateBuilder().setMultisigKey(brokenKey);
client.receiveMessage(initiateMsg.build());
pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, pair.clientRecorder.q.take());
}
@Test
public void testClientResumeNothing() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.CHANNEL_OPEN).build());
pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, pair.clientRecorder.q.take());
}
@Test
public void testClientRandomMessage() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
client.connectionOpen();
pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
// Send a CLIENT_VERSION back to the client - ?!?!!
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.CLIENT_VERSION).build());
Protos.TwoWayChannelMessage error = pair.clientRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(Protos.Error.ErrorCode.SYNTAX_ERROR, error.getError().getCode());
assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, pair.clientRecorder.q.take());
}
@Test
public void testDontResumeEmptyChannels() throws Exception {
// Check that if the client has an empty channel that's being kept around in case we need to broadcast the
// refund, we don't accidentally try to resume it).
// Open up a normal channel.
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND));
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT));
broadcasts.take();
pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
Sha256Hash contractHash = (Sha256Hash) pair.serverRecorder.q.take();
pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
// Send the whole channel at once. The server will broadcast the final contract and settle the channel for us.
client.incrementPayment(client.state().getValueRefunded());
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
broadcasts.take();
// The channel is now empty.
assertEquals(Coin.ZERO, client.state().getValueRefunded());
pair.serverRecorder.q.take(); // Take the Coin.
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CLOSE));
assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, pair.clientRecorder.q.take());
client.connectionClosed();
// Now try opening a new channel with the same server ID and verify the client asks for a new channel.
client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
client.connectionOpen();
Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
assertFalse(msg.getClientVersion().hasPreviousChannelContractHash());
}
@Test
public void repeatedChannels() throws Exception {
// Ensures we're selecting channels correctly. Covers a bug in which we'd always try and fail to resume
// the first channel due to lack of proper closing behaviour.
// Open up a normal channel, but don't spend all of it, then settle it.
{
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND));
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT));
broadcasts.take();
pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
Sha256Hash contractHash = (Sha256Hash) pair.serverRecorder.q.take();
pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
for (int i = 0; i < 3; i++) {
ListenableFuture<PaymentIncrementAck> future = client.incrementPayment(CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
pair.serverRecorder.q.take();
final Protos.TwoWayChannelMessage msg = pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK);
final Protos.PaymentAck paymentAck = msg.getPaymentAck();
assertTrue("No PaymentAck.Info", paymentAck.hasInfo());
assertEquals("Wrong PaymentAck info", ByteString.copyFromUtf8(CENT.toPlainString()), paymentAck.getInfo());
client.receiveMessage(msg);
assertTrue(future.isDone());
final PaymentIncrementAck paymentIncrementAck = future.get();
assertEquals("Wrong value returned from increasePayment", CENT, paymentIncrementAck.getValue());
assertEquals("Wrong info returned from increasePayment", ByteString.copyFromUtf8(CENT.toPlainString()), paymentIncrementAck.getInfo());
}
// Settle it and verify it's considered to be settled.
broadcastTxPause.release();
client.settle();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLOSE));
Transaction settlement1 = broadcasts.take();
// Server sends back the settle TX it just broadcast.
final Protos.TwoWayChannelMessage closeMsg = pair.serverRecorder.checkNextMsg(MessageType.CLOSE);
final Transaction settlement2 = new Transaction(params, closeMsg.getSettlement().getTx().toByteArray());
assertEquals(settlement1, settlement2);
client.receiveMessage(closeMsg);
assertNotNull(wallet.getTransaction(settlement2.getHash())); // Close TX entered the wallet.
sendMoneyToWallet(settlement1, AbstractBlockChain.NewBlockType.BEST_CHAIN);
client.connectionClosed();
server.connectionClosed();
}
// Now open a second channel and don't spend all of it/don't settle it.
{
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
final Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
assertFalse(msg.getClientVersion().hasPreviousChannelContractHash());
server.receiveMessage(msg);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE));
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND));
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT));
broadcasts.take();
pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
Sha256Hash contractHash = (Sha256Hash) pair.serverRecorder.q.take();
pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
client.incrementPayment(CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
client.connectionClosed();
server.connectionClosed();
}
// Now connect again and check we resume the second channel.
{
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
}
assertEquals(2, StoredPaymentChannelClientStates.getFromWallet(wallet).mapChannels.size());
}
}

View File

@ -1,170 +0,0 @@
package com.dogecoin.dogecoinj.protocols.channels;
import com.dogecoin.dogecoinj.core.Coin;
import com.dogecoin.dogecoinj.core.Sha256Hash;
import com.dogecoin.dogecoinj.core.TransactionBroadcaster;
import com.dogecoin.dogecoinj.core.Wallet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import javax.annotation.Nullable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static org.junit.Assert.assertEquals;
/**
* Various mock objects and utilities for testing payment channels code.
*/
public class ChannelTestUtils {
public static class RecordingServerConnection implements PaymentChannelServer.ServerConnection {
public BlockingQueue<Object> q = new LinkedBlockingQueue<Object>();
@Override
public void sendToClient(Protos.TwoWayChannelMessage msg) {
q.add(msg);
}
@Override
public void destroyConnection(PaymentChannelCloseException.CloseReason reason) {
q.add(reason);
}
@Override
public void channelOpen(Sha256Hash contractHash) {
q.add(contractHash);
}
@Override
public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
q.add(new UpdatePair(to, info));
return Futures.immediateFuture(ByteString.copyFromUtf8(by.toPlainString()));
}
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
return (Protos.TwoWayChannelMessage) q.take();
}
public Protos.TwoWayChannelMessage checkNextMsg(Protos.TwoWayChannelMessage.MessageType expectedType) throws InterruptedException {
Protos.TwoWayChannelMessage msg = getNextMsg();
assertEquals(expectedType, msg.getType());
return msg;
}
public void checkTotalPayment(Coin valueSoFar) throws InterruptedException {
Coin lastSeen = ((UpdatePair) q.take()).amount;
assertEquals(lastSeen, valueSoFar);
}
}
public static class RecordingClientConnection implements PaymentChannelClient.ClientConnection {
public BlockingQueue<Object> q = new LinkedBlockingQueue<Object>();
final static int IGNORE_EXPIRE = -1;
private final int maxExpireTime;
// An arbitrary sentinel object for equality testing.
public static final Object CHANNEL_INITIATED = new Object();
public static final Object CHANNEL_OPEN = new Object();
public RecordingClientConnection(int maxExpireTime) {
this.maxExpireTime = maxExpireTime;
}
@Override
public void sendToServer(Protos.TwoWayChannelMessage msg) {
q.add(msg);
}
@Override
public void destroyConnection(PaymentChannelCloseException.CloseReason reason) {
q.add(reason);
}
@Override
public boolean acceptExpireTime(long expireTime) {
return this.maxExpireTime == IGNORE_EXPIRE || expireTime <= maxExpireTime;
}
@Override
public void channelOpen(boolean wasInitiated) {
if (wasInitiated)
q.add(CHANNEL_INITIATED);
q.add(CHANNEL_OPEN);
}
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
return (Protos.TwoWayChannelMessage) q.take();
}
public Protos.TwoWayChannelMessage checkNextMsg(Protos.TwoWayChannelMessage.MessageType expectedType) throws InterruptedException {
Protos.TwoWayChannelMessage msg = getNextMsg();
assertEquals(expectedType, msg.getType());
return msg;
}
public void checkOpened() throws InterruptedException {
assertEquals(CHANNEL_OPEN, q.take());
}
public void checkInitiated() throws InterruptedException {
assertEquals(CHANNEL_INITIATED, q.take());
checkOpened();
}
}
public static class RecordingPair {
public PaymentChannelServer server;
public RecordingServerConnection serverRecorder;
public RecordingClientConnection clientRecorder;
}
public static RecordingPair makeRecorders(final Wallet serverWallet, final TransactionBroadcaster mockBroadcaster) {
return makeRecorders(serverWallet, mockBroadcaster, RecordingClientConnection.IGNORE_EXPIRE);
}
public static RecordingPair makeRecorders(final Wallet serverWallet, final TransactionBroadcaster mockBroadcaster, int maxExpireTime) {
RecordingPair pair = new RecordingPair();
pair.serverRecorder = new RecordingServerConnection();
pair.server = new PaymentChannelServer(mockBroadcaster, serverWallet, Coin.COIN, pair.serverRecorder);
pair.clientRecorder = new RecordingClientConnection(maxExpireTime);
return pair;
}
public static class UpdatePair {
public Coin amount;
public ByteString info;
public UpdatePair(Coin amount, ByteString info) {
this.amount = amount;
this.info = info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UpdatePair that = (UpdatePair) o;
if (amount != null ? !amount.equals(that.amount) : that.amount != null) return false;
if (info != null ? !info.equals(that.info) : that.info != null) return false;
return true;
}
@Override
public int hashCode() {
int result = amount != null ? amount.hashCode() : 0;
result = 31 * result + (info != null ? info.hashCode() : 0);
return result;
}
public void assertPair(Coin amount, ByteString info) {
assertEquals(amount, this.amount);
assertEquals(info, this.info);
}
}
}

View File

@ -1,73 +0,0 @@
package com.dogecoin.dogecoinj.protocols.channels;
import com.dogecoin.dogecoinj.core.*;
import org.bitcoin.paymentchannel.Protos;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.HashMap;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.*;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
public class PaymentChannelClientTest {
private static final int CLIENT_MAJOR_VERSION = 1;
private Wallet wallet;
private ECKey ecKey;
private Sha256Hash serverHash;
private IPaymentChannelClient.ClientConnection connection;
public Coin maxValue;
public Capture<TwoWayChannelMessage> clientVersionCapture;
public int defaultTimeWindow = 86340;
@Before
public void before() {
wallet = createMock(Wallet.class);
ecKey = createMock(ECKey.class);
maxValue = Coin.COIN;
serverHash = Sha256Hash.create("serverId".getBytes());
connection = createMock(IPaymentChannelClient.ClientConnection.class);
clientVersionCapture = new Capture<TwoWayChannelMessage>();
}
@Test
public void shouldSendClientVersionOnChannelOpen() throws Exception {
PaymentChannelClient dut = new PaymentChannelClient(wallet, ecKey, maxValue, serverHash, connection);
connection.sendToServer(capture(clientVersionCapture));
EasyMock.expect(wallet.getExtensions()).andReturn(new HashMap<String, WalletExtension>());
replay(connection, wallet);
dut.connectionOpen();
assertClientVersion(defaultTimeWindow);
}
@Test
public void shouldSendTimeWindowInClientVersion() throws Exception {
long timeWindow = 4000;
KeyParameter userKey = null;
PaymentChannelClient dut =
new PaymentChannelClient(wallet, ecKey, maxValue, serverHash, timeWindow, userKey, connection);
connection.sendToServer(capture(clientVersionCapture));
EasyMock.expect(wallet.getExtensions()).andReturn(new HashMap<String, WalletExtension>());
replay(connection, wallet);
dut.connectionOpen();
assertClientVersion(4000);
}
private void assertClientVersion(long expectedTimeWindow) {
final TwoWayChannelMessage response = clientVersionCapture.getValue();
final TwoWayChannelMessage.MessageType type = response.getType();
assertEquals("Wrong type " + type, CLIENT_VERSION, type);
final Protos.ClientVersion clientVersion = response.getClientVersion();
final int major = clientVersion.getMajor();
assertEquals("Wrong major version " + major, CLIENT_MAJOR_VERSION, major);
final long actualTimeWindow = clientVersion.getTimeWindowSecs();
assertEquals("Wrong timeWindow " + actualTimeWindow, expectedTimeWindow, actualTimeWindow );
}
}

View File

@ -1,149 +0,0 @@
package com.dogecoin.dogecoinj.protocols.channels;
import com.dogecoin.dogecoinj.core.Coin;
import com.dogecoin.dogecoinj.core.TransactionBroadcaster;
import com.dogecoin.dogecoinj.core.Utils;
import com.dogecoin.dogecoinj.core.Wallet;
import org.bitcoin.paymentchannel.Protos;
import org.easymock.Capture;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.assertTrue;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertEquals;
public class PaymentChannelServerTest {
private static final int CLIENT_MAJOR_VERSION = 1;
private static final long SERVER_MAJOR_VERSION = 1;
public Wallet wallet;
public PaymentChannelServer.ServerConnection connection;
public PaymentChannelServer dut;
public Capture<? extends TwoWayChannelMessage> serverVersionCapture;
private TransactionBroadcaster broadcaster;
@Before
public void setUp() {
broadcaster = createMock(TransactionBroadcaster.class);
wallet = createMock(Wallet.class);
connection = createMock(PaymentChannelServer.ServerConnection.class);
serverVersionCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(serverVersionCapture));
Utils.setMockClock();
}
@Test
public void shouldAcceptDefaultTimeWindow() {
final TwoWayChannelMessage message = createClientVersionMessage();
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + 24 * 60 * 60 - 60; // This the default defined in paymentchannel.proto
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test
public void shouldTruncateTooSmallTimeWindow() {
final int minTimeWindow = 20000;
final int timeWindow = minTimeWindow - 1;
final TwoWayChannelMessage message = createClientVersionMessage(timeWindow);
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, minTimeWindow, 40000, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + minTimeWindow;
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test
public void shouldTruncateTooLargeTimeWindow() {
final int maxTimeWindow = 40000;
final int timeWindow = maxTimeWindow + 1;
final TwoWayChannelMessage message = createClientVersionMessage(timeWindow);
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, 20000, maxTimeWindow, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + maxTimeWindow;
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test(expected = IllegalArgumentException.class)
public void shouldNotAllowTimeWindowLessThan2h() {
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, 7199, 40000, connection);
}
@Test(expected = IllegalArgumentException.class)
public void shouldNotAllowNegativeTimeWindow() {
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, 40001, 40000, connection);
}
@Test
public void shouldAllowExactTimeWindow() {
final TwoWayChannelMessage message = createClientVersionMessage();
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
final int expire = 24 * 60 * 60 - 60; // This the default defined in paymentchannel.proto
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, expire, expire, connection);
dut.connectionOpen();
long expectedExpire = Utils.currentTimeSeconds() + expire;
dut.receiveMessage(message);
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
private void assertServerVersion() {
final TwoWayChannelMessage response = serverVersionCapture.getValue();
final MessageType type = response.getType();
assertEquals("Wrong type " + type, MessageType.SERVER_VERSION, type);
final long major = response.getServerVersion().getMajor();
assertEquals("Wrong major version", SERVER_MAJOR_VERSION, major);
}
private void assertExpireTime(long expectedExpire, Capture<TwoWayChannelMessage> initiateCapture) {
final TwoWayChannelMessage response = initiateCapture.getValue();
final MessageType type = response.getType();
assertEquals("Wrong type " + type, MessageType.INITIATE, type);
final long actualExpire = response.getInitiate().getExpireTimeSecs();
assertTrue("Expire time too small " + expectedExpire + " > " + actualExpire, expectedExpire <= actualExpire);
assertTrue("Expire time too large " + expectedExpire + "<" + actualExpire, expectedExpire >= actualExpire);
}
private TwoWayChannelMessage createClientVersionMessage() {
final Protos.ClientVersion.Builder clientVersion = Protos.ClientVersion.newBuilder().setMajor(CLIENT_MAJOR_VERSION);
return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build();
}
private TwoWayChannelMessage createClientVersionMessage(long timeWindow) {
final Protos.ClientVersion.Builder clientVersion = Protos.ClientVersion.newBuilder().setMajor(CLIENT_MAJOR_VERSION);
if (timeWindow > 0) clientVersion.setTimeWindowSecs(timeWindow);
return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build();
}
}

View File

@ -1,805 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.protocols.channels;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.script.Script;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.testing.TestWithWallet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeTx;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.makeSolvedTestBlock;
import static org.junit.Assert.*;
public class PaymentChannelStateTest extends TestWithWallet {
private ECKey serverKey;
private Coin halfCoin;
private Wallet serverWallet;
private PaymentChannelServerState serverState;
private PaymentChannelClientState clientState;
private TransactionBroadcaster mockBroadcaster;
private BlockingQueue<TxFuturePair> broadcasts;
private static class TxFuturePair {
Transaction tx;
SettableFuture<Transaction> future;
public TxFuturePair(Transaction tx, SettableFuture<Transaction> future) {
this.tx = tx;
this.future = future;
}
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
wallet.addExtension(new StoredPaymentChannelClientStates(wallet, new TransactionBroadcaster() {
@Override
public ListenableFuture<Transaction> broadcastTransaction(Transaction tx) {
fail();
return null;
}
}));
sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
chain = new BlockChain(params, wallet, blockStore); // Recreate chain as sendMoneyToWallet will confuse it
serverWallet = new Wallet(params);
serverKey = serverWallet.freshReceiveKey();
chain.addWallet(serverWallet);
halfCoin = valueOf(0, 50);
broadcasts = new LinkedBlockingQueue<TxFuturePair>();
mockBroadcaster = new TransactionBroadcaster() {
@Override
public ListenableFuture<Transaction> broadcastTransaction(Transaction tx) {
SettableFuture<Transaction> future = SettableFuture.create();
broadcasts.add(new TxFuturePair(tx, future));
return future;
}
};
}
@After
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void stateErrors() throws Exception {
PaymentChannelClientState channelState = new PaymentChannelClientState(wallet, myKey, serverKey,
COIN.multiply(10), 20);
assertEquals(PaymentChannelClientState.State.NEW, channelState.getState());
try {
channelState.getMultisigContract();
fail();
} catch (IllegalStateException e) {
// Expected.
}
try {
channelState.initiate();
fail();
} catch (InsufficientMoneyException e) {
}
}
@Test
public void basic() throws Exception {
// Check it all works when things are normal (no attacks, no problems).
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeSeconds() + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), halfCoin, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Send the refund tx from client to server and get back the signature.
Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
// Validate the multisig contract looks right.
Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
Script script = multisigContract.getOutput(0).getScriptPubKey();
assertTrue(script.isSentToMultiSig());
script = multisigContract.getOutput(1).getScriptPubKey();
assertTrue(script.isSentToAddress());
assertTrue(wallet.getPendingTransactions().contains(multisigContract));
// Provide the server with the multisig contract and simulate successful propagation/acceptance.
serverState.provideMultiSigContract(multisigContract);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
final TxFuturePair pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Make sure the refund transaction is not in the wallet and multisig contract's output is not connected to it
assertEquals(2, wallet.getTransactions(false).size());
Iterator<Transaction> walletTransactionIterator = wallet.getTransactions(false).iterator();
Transaction clientWalletMultisigContract = walletTransactionIterator.next();
assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
if (!clientWalletMultisigContract.getHash().equals(multisigContract.getHash())) {
clientWalletMultisigContract = walletTransactionIterator.next();
assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
} else
assertFalse(walletTransactionIterator.next().getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
assertEquals(multisigContract.getHash(), clientWalletMultisigContract.getHash());
assertFalse(clientWalletMultisigContract.getInput(0).getConnectedOutput().getSpentBy().getParentTransaction().getHash().equals(refund.getHash()));
// Both client and server are now in the ready state. Simulate a few micropayments of 0.005 bitcoins.
Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO;
for (int i = 0; i < 4; i++) {
byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
}
// Now confirm the contract transaction and make sure payments still work
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract));
byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
// And settle the channel.
serverState.close();
assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
final TxFuturePair pair2 = broadcasts.take();
Transaction closeTx = pair2.tx;
pair2.future.set(closeTx);
final Transaction reserializedCloseTx = new Transaction(params, closeTx.bitcoinSerialize());
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
// ... and on the client side.
wallet.receivePending(reserializedCloseTx, null);
assertEquals(PaymentChannelClientState.State.CLOSED, clientState.getState());
// Create a block with the payment transaction in it and give it to both wallets
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), reserializedCloseTx));
assertEquals(size.multiply(5), serverWallet.getBalance());
assertEquals(0, serverWallet.getPendingTransactions().size());
assertEquals(COIN.subtract(size.multiply(5)), wallet.getBalance());
assertEquals(0, wallet.getPendingTransactions().size());
assertEquals(3, wallet.getTransactions(false).size());
walletTransactionIterator = wallet.getTransactions(false).iterator();
Transaction clientWalletCloseTransaction = walletTransactionIterator.next();
if (!clientWalletCloseTransaction.getHash().equals(closeTx.getHash()))
clientWalletCloseTransaction = walletTransactionIterator.next();
if (!clientWalletCloseTransaction.getHash().equals(closeTx.getHash()))
clientWalletCloseTransaction = walletTransactionIterator.next();
assertEquals(closeTx.getHash(), clientWalletCloseTransaction.getHash());
assertNotNull(clientWalletCloseTransaction.getInput(0).getConnectedOutput());
}
@Test
public void setupDoS() throws Exception {
// Check that if the other side stops after we have provided a signed multisig contract, that after a timeout
// we can broadcast the refund and get our balance back.
// Spend the client wallet's one coin
Transaction spendCoinTx = wallet.sendCoinsOffline(Wallet.SendRequest.to(new ECKey().toAddress(params), COIN));
assertEquals(Coin.ZERO, wallet.getBalance());
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), spendCoinTx, createFakeTx(params, CENT, myAddress)));
assertEquals(CENT, wallet.getBalance());
// Set the wallet's stored states to use our real test PeerGroup
StoredPaymentChannelClientStates stateStorage = new StoredPaymentChannelClientStates(wallet, mockBroadcaster);
wallet.addOrUpdateExtension(stateStorage);
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()),
CENT.divide(2), EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
assertEquals(CENT.divide(2), clientState.getTotalValue());
clientState.initiate();
// We will have to pay min_tx_fee twice - both the multisig contract and the refund tx
assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2));
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Send the refund tx from client to server and get back the signature.
Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
// Validate the multisig contract looks right.
Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
Script script = multisigContract.getOutput(0).getScriptPubKey();
assertTrue(script.isSentToMultiSig());
script = multisigContract.getOutput(1).getScriptPubKey();
assertTrue(script.isSentToAddress());
assertTrue(wallet.getPendingTransactions().contains(multisigContract));
// Provide the server with the multisig contract and simulate successful propagation/acceptance.
serverState.provideMultiSigContract(multisigContract);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
final TxFuturePair pop = broadcasts.take();
pop.future.set(pop.tx);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Pay a tiny bit
serverState.incrementPayment(CENT.divide(2).subtract(CENT.divide(10)),
clientState.incrementPaymentBy(CENT.divide(10), null).signature.encodeToBitcoin());
// Advance time until our we get close enough to lock time that server should rebroadcast
Utils.rollMockClock(60*60*22);
// ... and store server to get it to broadcast payment transaction
serverState.storeChannelInWallet(null);
TxFuturePair broadcastPaymentPair = broadcasts.take();
Exception paymentException = new RuntimeException("I'm sorry, but the network really just doesn't like you");
broadcastPaymentPair.future.setException(paymentException);
try {
serverState.close().get();
} catch (ExecutionException e) {
assertSame(e.getCause(), paymentException);
}
assertEquals(PaymentChannelServerState.State.ERROR, serverState.getState());
// Now advance until client should rebroadcast
Utils.rollMockClock(60 * 60 * 2 + 60 * 5);
// Now store the client state in a stored state object which handles the rebroadcasting
clientState.doStoreChannelInWallet(Sha256Hash.create(new byte[]{}));
TxFuturePair clientBroadcastedMultiSig = broadcasts.take();
TxFuturePair broadcastRefund = broadcasts.take();
assertEquals(clientBroadcastedMultiSig.tx.getHash(), multisigContract.getHash());
for (TransactionInput input : clientBroadcastedMultiSig.tx.getInputs())
input.verify();
clientBroadcastedMultiSig.future.set(clientBroadcastedMultiSig.tx);
Transaction clientBroadcastedRefund = broadcastRefund.tx;
assertEquals(clientBroadcastedRefund.getHash(), clientState.getCompletedRefundTransaction().getHash());
for (TransactionInput input : clientBroadcastedRefund.getInputs()) {
// If the multisig output is connected, the wallet will fail to deserialize
if (input.getOutpoint().getHash().equals(clientBroadcastedMultiSig.tx.getHash()))
assertNull(input.getConnectedOutput().getSpentBy());
input.verify(clientBroadcastedMultiSig.tx.getOutput(0));
}
broadcastRefund.future.set(clientBroadcastedRefund);
// Create a block with multisig contract and refund transaction in it and give it to both wallets,
// making getBalance() include the transactions
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract,clientBroadcastedRefund));
// Make sure we actually had to pay what initialize() told us we would
assertEquals(wallet.getBalance(), CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2)));
try {
// After its expired, we cant still increment payment
clientState.incrementPaymentBy(CENT, null);
fail();
} catch (IllegalStateException e) { }
}
@Test
public void checkBadData() throws Exception {
// Check that if signatures/transactions/etc are corrupted, the protocol rejects them correctly.
// We'll broadcast only one tx: multisig contract
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeSeconds() + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), halfCoin, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Test refund transaction with any number of issues
byte[] refundTxBytes = clientState.getIncompleteRefundTransaction().bitcoinSerialize();
Transaction refund = new Transaction(params, refundTxBytes);
refund.addOutput(Coin.ZERO, new ECKey().toAddress(params));
try {
serverState.provideRefundTransaction(refund, myKey.getPubKey());
fail();
} catch (VerificationException e) {}
refund = new Transaction(params, refundTxBytes);
refund.addInput(new TransactionInput(params, refund, new byte[] {}, new TransactionOutPoint(params, 42, refund.getHash())));
try {
serverState.provideRefundTransaction(refund, myKey.getPubKey());
fail();
} catch (VerificationException e) {}
refund = new Transaction(params, refundTxBytes);
refund.setLockTime(0);
try {
serverState.provideRefundTransaction(refund, myKey.getPubKey());
fail();
} catch (VerificationException e) {}
refund = new Transaction(params, refundTxBytes);
refund.getInput(0).setSequenceNumber(TransactionInput.NO_SEQUENCE);
try {
serverState.provideRefundTransaction(refund, myKey.getPubKey());
fail();
} catch (VerificationException e) {}
refund = new Transaction(params, refundTxBytes);
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
try { serverState.provideRefundTransaction(refund, myKey.getPubKey()); fail(); } catch (IllegalStateException e) {}
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
byte[] refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[refundSigCopy.length-1] = (byte) (Transaction.SigHash.NONE.ordinal() + 1);
try {
clientState.provideRefundSignature(refundSigCopy, null);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("SIGHASH_NONE"));
}
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[3] ^= 0x42; // Make the signature fail standard checks
try {
clientState.provideRefundSignature(refundSigCopy, null);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("not canonical"));
}
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard)
try {
clientState.provideRefundSignature(refundSigCopy, null);
fail();
} catch (VerificationException e) {
assertFalse(e.getMessage().contains("not canonical"));
}
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
try { clientState.getCompletedRefundTransaction(); fail(); } catch (IllegalStateException e) {}
clientState.provideRefundSignature(refundSigCopy, null);
try { clientState.provideRefundSignature(refundSigCopy, null); fail(); } catch (IllegalStateException e) {}
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
try { clientState.incrementPaymentBy(Coin.SATOSHI, null); fail(); } catch (IllegalStateException e) {}
byte[] multisigContractSerialized = clientState.getMultisigContract().bitcoinSerialize();
Transaction multisigContract = new Transaction(params, multisigContractSerialized);
multisigContract.clearOutputs();
multisigContract.addOutput(halfCoin, ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(serverKey, myKey)));
try {
serverState.provideMultiSigContract(multisigContract);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("client and server in that order"));
}
multisigContract = new Transaction(params, multisigContractSerialized);
multisigContract.clearOutputs();
multisigContract.addOutput(Coin.ZERO, ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(myKey, serverKey)));
try {
serverState.provideMultiSigContract(multisigContract);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("zero value"));
}
multisigContract = new Transaction(params, multisigContractSerialized);
multisigContract.clearOutputs();
multisigContract.addOutput(new TransactionOutput(params, multisigContract, halfCoin, new byte[] {0x01}));
try {
serverState.provideMultiSigContract(multisigContract);
fail();
} catch (VerificationException e) {}
multisigContract = new Transaction(params, multisigContractSerialized);
ListenableFuture<PaymentChannelServerState> multisigStateFuture = serverState.provideMultiSigContract(multisigContract);
try { serverState.provideMultiSigContract(multisigContract); fail(); } catch (IllegalStateException e) {}
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
assertFalse(multisigStateFuture.isDone());
final TxFuturePair pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(multisigStateFuture.get(), serverState);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Both client and server are now in the ready state. Simulate a few micropayments of 0.005 bitcoins.
Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO;
try {
clientState.incrementPaymentBy(COIN, null);
fail();
} catch (ValueOutOfRangeException e) {}
byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size);
byte[] signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.NONE.ordinal() + 1) | 0x80);
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();
} catch (VerificationException e) {}
signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[2] ^= 0x42; // Make the signature fail standard checks
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("not canonical"));
}
signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard)
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();
} catch (VerificationException e) {
assertFalse(e.getMessage().contains("not canonical"));
}
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
// Pay the rest (signed with SIGHASH_NONE|SIGHASH_ANYONECANPAY)
byte[] signature2 = clientState.incrementPaymentBy(halfCoin.subtract(totalPayment), null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(halfCoin.subtract(totalPayment));
assertEquals(totalPayment, halfCoin);
signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.SINGLE.ordinal() + 1) | 0x80);
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();
} catch (VerificationException e) {}
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature2);
// Trying to take reduce the refund size fails.
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment.subtract(size)), signature);
fail();
} catch (ValueOutOfRangeException e) {}
assertEquals(serverState.getBestValueToMe(), totalPayment);
try {
clientState.incrementPaymentBy(Coin.SATOSHI.negate(), null);
fail();
} catch (ValueOutOfRangeException e) {}
try {
clientState.incrementPaymentBy(halfCoin.subtract(size).add(Coin.SATOSHI), null);
fail();
} catch (ValueOutOfRangeException e) {}
}
@Test
public void feesTest() throws Exception {
// Test that transactions are getting the necessary fees
// Spend the client wallet's one coin
wallet.sendCoinsOffline(Wallet.SendRequest.to(new ECKey().toAddress(params), COIN));
assertEquals(Coin.ZERO, wallet.getBalance());
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), createFakeTx(params, CENT, myAddress)));
assertEquals(CENT, wallet.getBalance());
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
// Clearly SATOSHI is far too small to be useful
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), Coin.SATOSHI, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
try {
clientState.initiate();
fail();
} catch (ValueOutOfRangeException e) {}
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()),
Transaction.MIN_NONDUST_OUTPUT.subtract(Coin.SATOSHI).add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE),
EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
try {
clientState.initiate();
fail();
} catch (ValueOutOfRangeException e) {}
// Verify that MIN_NONDUST_OUTPUT + MIN_TX_FEE is accepted
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()),
Transaction.MIN_NONDUST_OUTPUT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
// We'll have to pay REFERENCE_DEFAULT_MIN_TX_FEE twice (multisig+refund), and we'll end up getting back nearly nothing...
clientState.initiate();
assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2));
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Now actually use a more useful CENT
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), CENT, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(clientState.getRefundTxFees(), Coin.ZERO);
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Send the refund tx from client to server and get back the signature.
Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
// Get the multisig contract
Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
// Provide the server with the multisig contract and simulate successful propagation/acceptance.
serverState.provideMultiSigContract(multisigContract);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
TxFuturePair pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Both client and server are now in the ready state. Simulate a few micropayments
Coin totalPayment = Coin.ZERO;
// We can send as little as we want - its up to the server to get the fees right
byte[] signature = clientState.incrementPaymentBy(Coin.SATOSHI, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(Coin.SATOSHI);
serverState.incrementPayment(CENT.subtract(totalPayment), signature);
// We can't refund more than the contract is worth...
try {
serverState.incrementPayment(CENT.add(SATOSHI), signature);
fail();
} catch (ValueOutOfRangeException e) {}
// We cannot send just under the total value - our refund would make it unspendable. So the client
// will correct it for us to be larger than the requested amount, to make the change output zero.
PaymentChannelClientState.IncrementedPayment payment =
clientState.incrementPaymentBy(CENT.subtract(Transaction.MIN_NONDUST_OUTPUT), null);
assertEquals(CENT.subtract(SATOSHI), payment.amount);
totalPayment = totalPayment.add(payment.amount);
// The server also won't accept it if we do that.
try {
serverState.incrementPayment(Transaction.MIN_NONDUST_OUTPUT.subtract(Coin.SATOSHI), signature);
fail();
} catch (ValueOutOfRangeException e) {}
serverState.incrementPayment(CENT.subtract(totalPayment), payment.signature.encodeToBitcoin());
// And settle the channel.
serverState.close();
assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
pair = broadcasts.take(); // settle
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
serverState.close();
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
}
@Test
public void serverAddsFeeTest() throws Exception {
// Test that the server properly adds the necessary fee at the end (or just drops the payment if its not worth it)
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), CENT, EXPIRE_TIME) {
@Override
protected void editContractSendRequest(Wallet.SendRequest req) {
req.coinSelector = wallet.getCoinSelector();
}
};
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Send the refund tx from client to server and get back the signature.
Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
// Validate the multisig contract looks right.
Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
Script script = multisigContract.getOutput(0).getScriptPubKey();
assertTrue(script.isSentToMultiSig());
script = multisigContract.getOutput(1).getScriptPubKey();
assertTrue(script.isSentToAddress());
assertTrue(wallet.getPendingTransactions().contains(multisigContract));
// Provide the server with the multisig contract and simulate successful propagation/acceptance.
serverState.provideMultiSigContract(multisigContract);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
TxFuturePair pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Both client and server are now in the ready state, split the channel in half
byte[] signature = clientState.incrementPaymentBy(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(Coin.SATOSHI), null)
.signature.encodeToBitcoin();
Coin totalRefund = CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(SATOSHI));
serverState.incrementPayment(totalRefund, signature);
// We need to pay MIN_TX_FEE, but we only have MIN_NONDUST_OUTPUT
try {
serverState.close();
fail();
} catch (InsufficientMoneyException e) {
}
// Now give the server enough coins to pay the fee
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, new ECKey().toAddress(params)), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(params, COIN, serverKey.toAddress(params));
serverWallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
// The contract is still not worth redeeming - its worth less than we pay in fee
try {
serverState.close();
fail();
} catch (InsufficientMoneyException e) {
assertTrue(e.getMessage().contains("more in fees"));
}
signature = clientState.incrementPaymentBy(SATOSHI.multiply(2), null).signature.encodeToBitcoin();
totalRefund = totalRefund.subtract(SATOSHI.multiply(2));
serverState.incrementPayment(totalRefund, signature);
// And settle the channel.
serverState.close();
assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
}
@Test
public void doubleSpendContractTest() throws Exception {
// Tests that if the client double-spends the multisig contract after it is sent, no more payments are accepted
// Start with a copy of basic()....
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeSeconds() + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
clientState = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), halfCoin, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
// Send the refund tx from client to server and get back the signature.
Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
// Validate the multisig contract looks right.
Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
Script script = multisigContract.getOutput(0).getScriptPubKey();
assertTrue(script.isSentToMultiSig());
script = multisigContract.getOutput(1).getScriptPubKey();
assertTrue(script.isSentToAddress());
assertTrue(wallet.getPendingTransactions().contains(multisigContract));
// Provide the server with the multisig contract and simulate successful propagation/acceptance.
serverState.provideMultiSigContract(multisigContract);
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
final TxFuturePair pair = broadcasts.take();
pair.future.set(pair.tx);
assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Make sure the refund transaction is not in the wallet and multisig contract's output is not connected to it
assertEquals(2, wallet.getTransactions(false).size());
Iterator<Transaction> walletTransactionIterator = wallet.getTransactions(false).iterator();
Transaction clientWalletMultisigContract = walletTransactionIterator.next();
assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
if (!clientWalletMultisigContract.getHash().equals(multisigContract.getHash())) {
clientWalletMultisigContract = walletTransactionIterator.next();
assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
} else
assertFalse(walletTransactionIterator.next().getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
assertEquals(multisigContract.getHash(), clientWalletMultisigContract.getHash());
assertFalse(clientWalletMultisigContract.getInput(0).getConnectedOutput().getSpentBy().getParentTransaction().getHash().equals(refund.getHash()));
// Both client and server are now in the ready state. Simulate a few micropayments of 0.005 bitcoins.
Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO;
for (int i = 0; i < 5; i++) {
byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
}
// Now create a double-spend and send it to the server
Transaction doubleSpendContract = new Transaction(params);
doubleSpendContract.addInput(new TransactionInput(params, doubleSpendContract, new byte[0],
multisigContract.getInput(0).getOutpoint()));
doubleSpendContract.addOutput(halfCoin, myKey);
doubleSpendContract = new Transaction(params, doubleSpendContract.bitcoinSerialize());
StoredBlock block = new StoredBlock(params.getGenesisBlock().createNextBlock(myKey.toAddress(params)), BigInteger.TEN, 1);
serverWallet.receiveFromBlock(doubleSpendContract, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
// Now if we try to spend again the server will reject it since it saw a double-spend
try {
byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
fail();
} catch (VerificationException e) {
assertTrue(e.getMessage().contains("double-spent"));
}
}
}

View File

@ -1,154 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.protocols.payments;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.X509Utils;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocol.Output;
import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocol.PkiVerificationData;
import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocolException.PkiVerificationException;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import org.bitcoin.protocols.payments.Protos;
import org.bitcoin.protocols.payments.Protos.Payment;
import org.bitcoin.protocols.payments.Protos.PaymentACK;
import org.bitcoin.protocols.payments.Protos.PaymentRequest;
import org.junit.Before;
import org.junit.Test;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.*;
public class PaymentProtocolTest {
// static test data
private static final NetworkParameters NETWORK_PARAMS = UnitTestParams.get();
private static final Coin AMOUNT = Coin.SATOSHI;
private static final Address TO_ADDRESS = new ECKey().toAddress(NETWORK_PARAMS);
private static final String MEMO = "memo";
private static final String PAYMENT_URL = "https://example.com";
private static final byte[] MERCHANT_DATA = new byte[] { 0, 1, 2 };
private KeyStore caStore;
private X509Certificate caCert;
@Before
public void setUp() throws Exception {
caStore = X509Utils.loadKeyStore("JKS", "password", getClass().getResourceAsStream("test-cacerts"));
caCert = (X509Certificate) caStore.getCertificate("test-cacert");
}
@Test
public void testSignAndVerifyValid() throws Exception {
Protos.PaymentRequest.Builder paymentRequest = minimalPaymentRequest().toBuilder();
// Sign
KeyStore keyStore = X509Utils
.loadKeyStore("JKS", "password", getClass().getResourceAsStream("test-valid-cert"));
PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-valid", "password".toCharArray());
X509Certificate clientCert = (X509Certificate) keyStore.getCertificate("test-valid");
PaymentProtocol.signPaymentRequest(paymentRequest, new X509Certificate[]{clientCert}, privateKey);
// Verify
PkiVerificationData verificationData = PaymentProtocol.verifyPaymentRequestPki(paymentRequest.build(), caStore);
assertNotNull(verificationData);
assertEquals(caCert, verificationData.rootAuthority.getTrustedCert());
}
@Test(expected = PkiVerificationException.class)
public void testSignAndVerifyExpired() throws Exception {
Protos.PaymentRequest.Builder paymentRequest = minimalPaymentRequest().toBuilder();
// Sign
KeyStore keyStore = X509Utils.loadKeyStore("JKS", "password",
getClass().getResourceAsStream("test-expired-cert"));
PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-expired", "password".toCharArray());
X509Certificate clientCert = (X509Certificate) keyStore.getCertificate("test-expired");
PaymentProtocol.signPaymentRequest(paymentRequest, new X509Certificate[]{clientCert}, privateKey);
// Verify
PaymentProtocol.verifyPaymentRequestPki(paymentRequest.build(), caStore);
}
private Protos.PaymentRequest minimalPaymentRequest() {
Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
paymentDetails.setTime(System.currentTimeMillis());
Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
return paymentRequest.build();
}
@Test
public void testPaymentRequest() throws Exception {
// Create
PaymentRequest paymentRequest = PaymentProtocol.createPaymentRequest(TestNet3Params.get(), AMOUNT, TO_ADDRESS, MEMO,
PAYMENT_URL, MERCHANT_DATA).build();
byte[] paymentRequestBytes = paymentRequest.toByteArray();
// Parse
PaymentSession parsedPaymentRequest = PaymentProtocol.parsePaymentRequest(PaymentRequest
.parseFrom(paymentRequestBytes));
final List<Output> parsedOutputs = parsedPaymentRequest.getOutputs();
assertEquals(1, parsedOutputs.size());
assertEquals(AMOUNT, parsedOutputs.get(0).amount);
assertArrayEquals(ScriptBuilder.createOutputScript(TO_ADDRESS).getProgram(), parsedOutputs.get(0).scriptData);
assertEquals(MEMO, parsedPaymentRequest.getMemo());
assertEquals(PAYMENT_URL, parsedPaymentRequest.getPaymentUrl());
assertArrayEquals(MERCHANT_DATA, parsedPaymentRequest.getMerchantData());
}
@Test
public void testPaymentMessage() throws Exception {
// Create
List<Transaction> transactions = new LinkedList<Transaction>();
transactions.add(FakeTxBuilder.createFakeTx(NETWORK_PARAMS, AMOUNT, TO_ADDRESS));
Coin refundAmount = Coin.SATOSHI;
Address refundAddress = new ECKey().toAddress(NETWORK_PARAMS);
Payment payment = PaymentProtocol.createPaymentMessage(transactions, refundAmount, refundAddress, MEMO,
MERCHANT_DATA);
byte[] paymentBytes = payment.toByteArray();
// Parse
Payment parsedPayment = Payment.parseFrom(paymentBytes);
List<Transaction> parsedTransactions = PaymentProtocol.parseTransactionsFromPaymentMessage(NETWORK_PARAMS,
parsedPayment);
assertEquals(transactions, parsedTransactions);
assertEquals(1, parsedPayment.getRefundToCount());
assertEquals(MEMO, parsedPayment.getMemo());
assertArrayEquals(MERCHANT_DATA, parsedPayment.getMerchantData().toByteArray());
}
@Test
public void testPaymentAck() throws Exception {
// Create
Payment paymentMessage = Protos.Payment.newBuilder().build();
PaymentACK paymentAck = PaymentProtocol.createPaymentAck(paymentMessage, MEMO);
byte[] paymentAckBytes = paymentAck.toByteArray();
// Parse
PaymentACK parsedPaymentAck = PaymentACK.parseFrom(paymentAckBytes);
assertEquals(paymentMessage, parsedPaymentAck.getPayment());
assertEquals(MEMO, parsedPaymentAck.getMemo());
}
}

View File

@ -1,224 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.protocols.payments;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.TrustStoreLoader;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.protocols.payments.Protos;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import static com.dogecoin.dogecoinj.core.Coin.COIN;
import static org.junit.Assert.*;
public class PaymentSessionTest {
private static final NetworkParameters params = TestNet3Params.get();
private static final String simplePaymentUrl = "http://a.simple.url.com/";
private static final String paymentRequestMemo = "send coinz noa plz kthx";
private static final String paymentMemo = "take ze coinz";
private static final ByteString merchantData = ByteString.copyFromUtf8("merchant data");
private static final long time = System.currentTimeMillis() / 1000L;
private ECKey serverKey;
private Transaction tx;
private TransactionOutput outputToMe;
private Coin coin = COIN;
@Before
public void setUp() throws Exception {
serverKey = new ECKey();
tx = new Transaction(params);
outputToMe = new TransactionOutput(params, tx, coin, serverKey);
tx.addOutput(outputToMe);
}
@Test
public void testSimplePayment() throws Exception {
// Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession.
MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest("test"));
assertEquals(paymentRequestMemo, paymentSession.getMemo());
assertEquals(coin, paymentSession.getValue());
assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl());
assertTrue(new Date(time * 1000L).equals(paymentSession.getDate()));
assertTrue(paymentSession.getSendRequest().tx.equals(tx));
assertFalse(paymentSession.isExpired());
// Send the payment and verify that the correct information is sent.
// Add a dummy input to tx so it is considered valid.
tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes()));
ArrayList<Transaction> txns = new ArrayList<Transaction>();
txns.add(tx);
Address refundAddr = new Address(params, serverKey.getPubKeyHash());
paymentSession.sendPayment(txns, refundAddr, paymentMemo);
assertEquals(1, paymentSession.getPaymentLog().size());
assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString());
Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment();
assertEquals(paymentMemo, payment.getMemo());
assertEquals(merchantData, payment.getMerchantData());
assertEquals(1, payment.getRefundToCount());
assertEquals(coin.value, payment.getRefundTo(0).getAmount());
TransactionOutput refundOutput = new TransactionOutput(params, null, coin, refundAddr);
ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes());
assertTrue(refundScript.equals(payment.getRefundTo(0).getScript()));
}
@Test
public void testDefaults() throws Exception {
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
.setTime(time)
.addOutputs(outputBuilder)
.build();
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder()
.setSerializedPaymentDetails(paymentDetails.toByteString())
.build();
MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
assertEquals(Coin.ZERO, paymentSession.getValue());
assertNull(paymentSession.getPaymentUrl());
assertNull(paymentSession.getMemo());
}
@Test
public void testExpiredPaymentRequest() throws Exception {
MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest());
assertTrue(paymentSession.isExpired());
// Send the payment and verify that an exception is thrown.
// Add a dummy input to tx so it is considered valid.
tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes()));
ArrayList<Transaction> txns = new ArrayList<Transaction>();
txns.add(tx);
try {
paymentSession.sendPayment(txns, null, null);
} catch(PaymentProtocolException.Expired e) {
assertEquals(0, paymentSession.getPaymentLog().size());
assertEquals(e.getMessage(), "PaymentRequest is expired");
return;
}
fail("Expected exception due to expired PaymentRequest");
}
@Test
public void testPkiVerification() throws Exception {
InputStream in = getClass().getResourceAsStream("pki_test.bitcoinpaymentrequest");
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder().mergeFrom(in).build();
PaymentProtocol.PkiVerificationData pkiData = PaymentProtocol.verifyPaymentRequestPki(paymentRequest,
new TrustStoreLoader.DefaultTrustStoreLoader().getKeyStore());
assertEquals("www.bitcoincore.org", pkiData.displayName);
assertEquals("The USERTRUST Network, Salt Lake City, US", pkiData.rootAuthorityName);
}
@Test(expected = PaymentProtocolException.InvalidNetwork.class)
public void testWrongNetwork() throws Exception {
// Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession.
MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest("main"));
assertEquals(MainNetParams.get(), paymentSession.getNetworkParameters());
// Send the payment and verify that the correct information is sent.
// Add a dummy input to tx so it is considered valid.
tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes()));
ArrayList<Transaction> txns = new ArrayList<Transaction>();
txns.add(tx);
Address refundAddr = new Address(params, serverKey.getPubKeyHash());
paymentSession.sendPayment(txns, refundAddr, paymentMemo);
assertEquals(1, paymentSession.getPaymentLog().size());
}
private Protos.PaymentRequest newSimplePaymentRequest(String netID) {
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
.setAmount(coin.value)
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
.setNetwork(netID)
.setTime(time)
.setPaymentUrl(simplePaymentUrl)
.addOutputs(outputBuilder)
.setMemo(paymentRequestMemo)
.setMerchantData(merchantData)
.build();
return Protos.PaymentRequest.newBuilder()
.setPaymentDetailsVersion(1)
.setPkiType("none")
.setSerializedPaymentDetails(paymentDetails.toByteString())
.build();
}
private Protos.PaymentRequest newExpiredPaymentRequest() {
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
.setAmount(coin.value)
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
.setNetwork("test")
.setTime(time - 10)
.setExpires(time - 1)
.setPaymentUrl(simplePaymentUrl)
.addOutputs(outputBuilder)
.setMemo(paymentRequestMemo)
.setMerchantData(merchantData)
.build();
return Protos.PaymentRequest.newBuilder()
.setPaymentDetailsVersion(1)
.setPkiType("none")
.setSerializedPaymentDetails(paymentDetails.toByteString())
.build();
}
private class MockPaymentSession extends PaymentSession {
private ArrayList<PaymentLogItem> paymentLog = new ArrayList<PaymentLogItem>();
public MockPaymentSession(Protos.PaymentRequest request) throws PaymentProtocolException {
super(request);
}
public ArrayList<PaymentLogItem> getPaymentLog() {
return paymentLog;
}
@Override
protected ListenableFuture<PaymentProtocol.Ack> sendPayment(final URL url, final Protos.Payment payment) {
paymentLog.add(new PaymentLogItem(url, payment));
return null;
}
public class PaymentLogItem {
private final URL url;
private final Protos.Payment payment;
PaymentLogItem(final URL url, final Protos.Payment payment) {
this.url = url;
this.payment = payment;
}
public URL getUrl() {
return url;
}
public Protos.Payment getPayment() {
return payment;
}
}
}
}

View File

@ -1,49 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.script;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_PUSHDATA1;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_PUSHDATA2;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_PUSHDATA4;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class ScriptChunkTest {
@Test
public void testShortestPossibleDataPush() {
assertTrue("empty push", new ScriptBuilder().data(new byte[0]).build().getChunks().get(0)
.isShortestPossiblePushData());
for (byte i = -1; i < 127; i++)
assertTrue("push of single byte " + i, new ScriptBuilder().data(new byte[] { i }).build().getChunks()
.get(0).isShortestPossiblePushData());
for (int len = 2; len < Script.MAX_SCRIPT_ELEMENT_SIZE; len++)
assertTrue("push of " + len + " bytes", new ScriptBuilder().data(new byte[len]).build().getChunks().get(0)
.isShortestPossiblePushData());
// non-standard chunks
for (byte i = 1; i <= 16; i++)
assertFalse("push of smallnum " + i, new ScriptChunk(1, new byte[] { i }).isShortestPossiblePushData());
assertFalse("push of 75 bytes", new ScriptChunk(OP_PUSHDATA1, new byte[75]).isShortestPossiblePushData());
assertFalse("push of 255 bytes", new ScriptChunk(OP_PUSHDATA2, new byte[255]).isShortestPossiblePushData());
assertFalse("push of 65535 bytes", new ScriptChunk(OP_PUSHDATA4, new byte[65535]).isShortestPossiblePushData());
}
}

View File

@ -1,405 +0,0 @@
/**
* Copyright 2011 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 com.dogecoin.dogecoinj.script;
import com.dogecoin.dogecoinj.core.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.dogecoin.dogecoinj.core.Transaction.SigHash;
import com.dogecoin.dogecoinj.crypto.TransactionSignature;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.dogecoin.dogecoinj.script.Script.VerifyFlag;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.*;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_0;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_INVALIDOPCODE;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
public class ScriptTest {
// From tx 05e04c26c12fe408a3c1b71aa7996403f6acad1045252b1c62e055496f4d2cb1 on the testnet.
static final String sigProg = "47304402202b4da291cc39faf8433911988f9f49fc5c995812ca2f94db61468839c228c3e90220628bff3ff32ec95825092fa051cba28558a981fcf59ce184b14f2e215e69106701410414b38f4be3bb9fa0f4f32b74af07152b2f2f630bc02122a491137b6c523e46f18a0d5034418966f93dfc37cc3739ef7b2007213a302b7fba161557f4ad644a1c";
static final String pubkeyProg = "76a91433e81a941e64cda12c6a299ed322ddbdd03f8d0e88ac";
static final NetworkParameters params = TestNet3Params.get();
private static final Logger log = LoggerFactory.getLogger(ScriptTest.class);
@Test
public void testScriptSig() throws Exception {
byte[] sigProgBytes = HEX.decode(sigProg);
Script script = new Script(sigProgBytes);
// Test we can extract the from address.
byte[] hash160 = Utils.sha256hash160(script.getPubKey());
Address a = new Address(params, hash160);
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", a.toString());
}
@Test
public void testScriptPubKey() throws Exception {
// Check we can extract the to address
byte[] pubkeyBytes = HEX.decode(pubkeyProg);
Script pubkey = new Script(pubkeyBytes);
assertEquals("DUP HASH160 PUSHDATA(20)[33e81a941e64cda12c6a299ed322ddbdd03f8d0e] EQUALVERIFY CHECKSIG", pubkey.toString());
Address toAddr = new Address(params, pubkey.getPubKeyHash());
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", toAddr.toString());
}
@Test
public void testMultiSig() throws Exception {
List<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey(), new ECKey());
assertTrue(ScriptBuilder.createMultiSigOutputScript(2, keys).isSentToMultiSig());
assertTrue(ScriptBuilder.createMultiSigOutputScript(3, keys).isSentToMultiSig());
assertFalse(ScriptBuilder.createOutputScript(new ECKey()).isSentToMultiSig());
try {
// Fail if we ask for more signatures than keys.
Script.createMultiSigOutputScript(4, keys);
fail();
} catch (Throwable e) {
// Expected.
}
try {
// Must have at least one signature required.
Script.createMultiSigOutputScript(0, keys);
} catch (Throwable e) {
// Expected.
}
// Actual execution is tested by the data driven tests.
}
@Test
public void testP2SHOutputScript() throws Exception {
Address p2shAddress = new Address(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
assertTrue(ScriptBuilder.createOutputScript(p2shAddress).isPayToScriptHash());
}
@Test
public void testIp() throws Exception {
byte[] bytes = HEX.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");
Script s = new Script(bytes);
assertTrue(s.isSentToRawPubKey());
}
@Test
public void testCreateMultiSigInputScript() throws AddressFormatException {
// Setup transaction and signatures
ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3));
byte[] bytes = HEX.decode("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000");
Transaction transaction = new Transaction(params, bytes);
TransactionOutput output = transaction.getOutput(1);
Transaction spendTx = new Transaction(params);
Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H");
Script outputScript = ScriptBuilder.createOutputScript(address);
spendTx.addOutput(output.getValue(), outputScript);
spendTx.addInput(output);
Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false);
ECKey.ECDSASignature party1Signature = key1.sign(sighash);
ECKey.ECDSASignature party2Signature = key2.sign(sighash);
TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false);
TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false);
// Create p2sh multisig input script
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript);
// Assert that the input script contains 4 chunks
assertTrue(inputScript.getChunks().size() == 4);
// Assert that the input script created contains the original multisig
// script as the last chunk
ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram());
// Create regular multisig input script
inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature));
// Assert that the input script only contains 3 chunks
assertTrue(inputScript.getChunks().size() == 3);
// Assert that the input script created does not end with the original
// multisig script
scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
Assert.assertThat(scriptChunk.data, IsNot.not(equalTo(multisigScript.getProgram())));
}
@Test
public void createAndUpdateEmptyInputScript() throws Exception {
TransactionSignature dummySig = TransactionSignature.dummy();
ECKey key = new ECKey();
// pay-to-pubkey
Script inputScript = ScriptBuilder.createInputScript(dummySig);
assertThat(inputScript.getChunks().get(0).data, equalTo(dummySig.encodeToBitcoin()));
inputScript = ScriptBuilder.createInputScript(null);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
// pay-to-address
inputScript = ScriptBuilder.createInputScript(dummySig, key);
assertThat(inputScript.getChunks().get(0).data, equalTo(dummySig.encodeToBitcoin()));
inputScript = ScriptBuilder.createInputScript(null, key);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(key.getPubKey()));
// pay-to-script-hash
ECKey key2 = new ECKey();
Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key, key2));
inputScript = ScriptBuilder.createP2SHMultiSigInputScript(Arrays.asList(dummySig, dummySig), multisigScript);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.createP2SHMultiSigInputScript(null, multisigScript);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(2).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 0, 1, 1);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 1, 1, 1);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
// updating scriptSig with no missing signatures
try {
ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 1, 1, 1);
fail("Exception expected");
} catch (Exception e) {
assertEquals(IllegalArgumentException.class, e.getClass());
}
}
private Script parseScriptString(String string) throws IOException {
String[] words = string.split("[ \\t\\n]");
UnsafeByteArrayOutputStream out = new UnsafeByteArrayOutputStream();
for(String w : words) {
if (w.equals(""))
continue;
if (w.matches("^-?[0-9]*$")) {
// Number
long val = Long.parseLong(w);
if (val >= -1 && val <= 16)
out.write(Script.encodeToOpN((int)val));
else
Script.writeBytes(out, Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(val), false)));
} else if (w.matches("^0x[0-9a-fA-F]*$")) {
// Raw hex data, inserted NOT pushed onto stack:
out.write(HEX.decode(w.substring(2).toLowerCase()));
} else if (w.length() >= 2 && w.startsWith("'") && w.endsWith("'")) {
// Single-quoted string, pushed as data. NOTE: this is poor-man's
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
Script.writeBytes(out, w.substring(1, w.length() - 1).getBytes(Charset.forName("UTF-8")));
} else if (ScriptOpCodes.getOpCode(w) != OP_INVALIDOPCODE) {
// opcode, e.g. OP_ADD or OP_1:
out.write(ScriptOpCodes.getOpCode(w));
} else if (w.startsWith("OP_") && ScriptOpCodes.getOpCode(w.substring(3)) != OP_INVALIDOPCODE) {
// opcode, e.g. OP_ADD or OP_1:
out.write(ScriptOpCodes.getOpCode(w.substring(3)));
} else {
throw new RuntimeException("Invalid Data");
}
}
return new Script(out.toByteArray());
}
private Set<VerifyFlag> parseVerifyFlags(String str) {
Set<VerifyFlag> flags = EnumSet.noneOf(VerifyFlag.class);
if (!"NONE".equals(str)) {
for (String flag : str.split(",")) {
try {
flags.add(VerifyFlag.valueOf(flag));
} catch (IllegalArgumentException x) {
log.debug("Cannot handle verify flag {} -- ignored.", flag);
}
}
}
return flags;
}
@Test
public void dataDrivenValidScripts() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"script_valid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
Script scriptSig = parseScriptString(test.get(0).asText());
Script scriptPubKey = parseScriptString(test.get(1).asText());
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
try {
scriptSig.correctlySpends(new Transaction(params), 0, scriptPubKey, verifyFlags);
} catch (ScriptException e) {
System.err.println(test);
System.err.flush();
throw e;
}
}
}
@Test
public void dataDrivenInvalidScripts() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"script_invalid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
try {
Script scriptSig = parseScriptString(test.get(0).asText());
Script scriptPubKey = parseScriptString(test.get(1).asText());
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
scriptSig.correctlySpends(new Transaction(params), 0, scriptPubKey, verifyFlags);
System.err.println(test);
System.err.flush();
fail();
} catch (VerificationException e) {
// Expected.
}
}
}
private Map<TransactionOutPoint, Script> parseScriptPubKeys(JsonNode inputs) throws IOException {
Map<TransactionOutPoint, Script> scriptPubKeys = new HashMap<TransactionOutPoint, Script>();
for (JsonNode input : inputs) {
String hash = input.get(0).asText();
int index = input.get(1).asInt();
String script = input.get(2).asText();
Sha256Hash sha256Hash = new Sha256Hash(HEX.decode(hash));
scriptPubKeys.put(new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script));
}
return scriptPubKeys;
}
@Test
public void dataDrivenValidTransactions() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"tx_valid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
if (test.isArray() && test.size() == 1 && test.get(0).isTextual())
continue; // This is a comment.
Transaction transaction = null;
try {
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
transaction = new Transaction(params, HEX.decode(test.get(1).asText().toLowerCase()));
transaction.verify();
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
for (int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i);
if (input.getOutpoint().getIndex() == 0xffffffffL)
input.getOutpoint().setIndex(-1);
assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
input.getScriptSig().correctlySpends(transaction, i, scriptPubKeys.get(input.getOutpoint()),
verifyFlags);
}
} catch (Exception e) {
System.err.println(test);
if (transaction != null)
System.err.println(transaction);
throw e;
}
}
}
@Test
public void dataDrivenInvalidTransactions() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"tx_invalid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
if (test.isArray() && test.size() == 1 && test.get(0).isTextual())
continue; // This is a comment.
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
Transaction transaction = new Transaction(params, HEX.decode(test.get(1).asText().toLowerCase()));
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
boolean valid = true;
try {
transaction.verify();
} catch (VerificationException e) {
valid = false;
}
// The reference client checks this case in CheckTransaction, but we leave it to
// later where we will see an attempt to double-spend, so we explicitly check here
HashSet<TransactionOutPoint> set = new HashSet<TransactionOutPoint>();
for (TransactionInput input : transaction.getInputs()) {
if (set.contains(input.getOutpoint()))
valid = false;
set.add(input.getOutpoint());
}
for (int i = 0; i < transaction.getInputs().size() && valid; i++) {
TransactionInput input = transaction.getInputs().get(i);
assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
try {
input.getScriptSig().correctlySpends(transaction, i, scriptPubKeys.get(input.getOutpoint()),
verifyFlags);
} catch (VerificationException e) {
valid = false;
}
}
if (valid)
fail();
}
}
@Test
public void getToAddress() throws Exception {
// pay to pubkey
ECKey toKey = new ECKey();
Address toAddress = toKey.toAddress(params);
assertEquals(toAddress, ScriptBuilder.createOutputScript(toKey).getToAddress(params, true));
// pay to pubkey hash
assertEquals(toAddress, ScriptBuilder.createOutputScript(toAddress).getToAddress(params, true));
// pay to script hash
Script p2shScript = ScriptBuilder.createP2SHOutputScript(new byte[20]);
Address scriptAddress = Address.fromP2SHScript(params, p2shScript);
assertEquals(scriptAddress, p2shScript.getToAddress(params, true));
}
@Test(expected = ScriptException.class)
public void getToAddressNoPubKey() throws Exception {
ScriptBuilder.createOutputScript(new ECKey()).getToAddress(params, false);
}
}

View File

@ -1,61 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.store;
import com.dogecoin.dogecoinj.core.Address;
import com.dogecoin.dogecoinj.core.ECKey;
import com.dogecoin.dogecoinj.core.NetworkParameters;
import com.dogecoin.dogecoinj.core.StoredBlock;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
public class SPVBlockStoreTest {
@Test
public void basics() throws Exception {
NetworkParameters params = UnitTestParams.get();
File f = File.createTempFile("spvblockstore", null);
f.delete();
f.deleteOnExit();
SPVBlockStore store = new SPVBlockStore(params, f);
Address to = new ECKey().toAddress(params);
// Check the first block in a new store is the genesis block.
StoredBlock genesis = store.getChainHead();
assertEquals(params.getGenesisBlock(), genesis.getHeader());
assertEquals(0, genesis.getHeight());
// Build a new block.
StoredBlock b1 = genesis.build(genesis.getHeader().createNextBlock(to).cloneAsHeader());
store.put(b1);
store.setChainHead(b1);
store.close();
// Check we can get it back out again if we rebuild the store object.
store = new SPVBlockStore(params, f);
StoredBlock b2 = store.get(b1.getHeader().getHash());
assertEquals(b1, b2);
// Check the chain head was stored correctly also.
StoredBlock chainHead = store.getChainHead();
assertEquals(b1, chainHead);
}
}

View File

@ -1,363 +0,0 @@
/**
* Copyright 2012 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 com.dogecoin.dogecoinj.store;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.core.TransactionConfidence.ConfidenceType;
import com.dogecoin.dogecoinj.crypto.DeterministicKey;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.testing.FooWalletExtension;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.utils.Threading;
import com.dogecoin.dogecoinj.wallet.DeterministicKeyChain;
import com.dogecoin.dogecoinj.wallet.KeyChain;
import com.google.protobuf.ByteString;
import com.dogecoin.dogecoinj.wallet.MarriedKeyChain;
import com.dogecoin.dogecoinj.wallet.Protos;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.testing.FakeTxBuilder.createFakeTx;
import static org.junit.Assert.*;
public class WalletProtobufSerializerTest {
static final NetworkParameters params = UnitTestParams.get();
private ECKey myKey;
private ECKey myWatchedKey;
private Address myAddress;
private Wallet myWallet;
public static String WALLET_DESCRIPTION = "The quick brown fox lives in \u4f26\u6566"; // Beijing in Chinese
private long mScriptCreationTime;
@Before
public void setUp() throws Exception {
BriefLogFormatter.initVerbose();
myWatchedKey = new ECKey();
myWallet = new Wallet(params);
myKey = new ECKey();
myKey.setCreationTimeSeconds(123456789L);
myWallet.importKey(myKey);
myAddress = myKey.toAddress(params);
myWallet = new Wallet(params);
myWallet.importKey(myKey);
mScriptCreationTime = new Date().getTime() / 1000 - 1234;
myWallet.addWatchedAddress(myWatchedKey.toAddress(params), mScriptCreationTime);
myWallet.setDescription(WALLET_DESCRIPTION);
}
@Test
public void empty() throws Exception {
// Check the base case of a wallet with one key and no transactions.
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
assertEquals(mScriptCreationTime,
wallet1.getWatchedScripts().get(0).getCreationTimeSeconds());
assertEquals(1, wallet1.getWatchedScripts().size());
assertEquals(ScriptBuilder.createOutputScript(myWatchedKey.toAddress(params)),
wallet1.getWatchedScripts().get(0));
assertEquals(WALLET_DESCRIPTION, wallet1.getDescription());
}
@Test
public void oneTx() throws Exception {
// Check basic tx serialization.
Coin v1 = COIN;
Transaction t1 = createFakeTx(params, v1, myAddress);
t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("1.2.3.4")));
t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("5.6.7.8")));
t1.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
myWallet.receivePending(t1, null);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true).size());
assertEquals(v1, wallet1.getBalance(Wallet.BalanceType.ESTIMATED));
Transaction t1copy = wallet1.getTransaction(t1.getHash());
assertArrayEquals(t1.bitcoinSerialize(), t1copy.bitcoinSerialize());
assertEquals(2, t1copy.getConfidence().numBroadcastPeers());
assertEquals(TransactionConfidence.Source.NETWORK, t1copy.getConfidence().getSource());
Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(myWallet);
assertEquals(Protos.Key.Type.ORIGINAL, walletProto.getKey(0).getType());
assertEquals(0, walletProto.getExtensionCount());
assertEquals(1, walletProto.getTransactionCount());
assertEquals(6, walletProto.getKeyCount());
Protos.Transaction t1p = walletProto.getTransaction(0);
assertEquals(0, t1p.getBlockHashCount());
assertArrayEquals(t1.getHash().getBytes(), t1p.getHash().toByteArray());
assertEquals(Protos.Transaction.Pool.PENDING, t1p.getPool());
assertFalse(t1p.hasLockTime());
assertFalse(t1p.getTransactionInput(0).hasSequence());
assertArrayEquals(t1.getInputs().get(0).getOutpoint().getHash().getBytes(),
t1p.getTransactionInput(0).getTransactionOutPointHash().toByteArray());
assertEquals(0, t1p.getTransactionInput(0).getTransactionOutPointIndex());
assertEquals(t1p.getTransactionOutput(0).getValue(), v1.value);
}
@Test
public void doubleSpend() throws Exception {
// Check that we can serialize double spends correctly, as this is a slightly tricky case.
FakeTxBuilder.DoubleSpends doubleSpends = FakeTxBuilder.createFakeDoubleSpendTxns(params, myAddress);
// t1 spends to our wallet.
myWallet.receivePending(doubleSpends.t1, null);
// t2 rolls back t1 and spends somewhere else.
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true).size());
Transaction t1 = wallet1.getTransaction(doubleSpends.t1.getHash());
assertEquals(ConfidenceType.DEAD, t1.getConfidence().getConfidenceType());
assertEquals(Coin.ZERO, wallet1.getBalance());
// TODO: Wallet should store overriding transactions even if they are not wallet-relevant.
// assertEquals(doubleSpends.t2, t1.getConfidence().getOverridingTransaction());
}
@Test
public void testKeys() throws Exception {
for (int i = 0 ; i < 20 ; i++) {
myKey = new ECKey();
myAddress = myKey.toAddress(params);
myWallet = new Wallet(params);
myWallet.importKey(myKey);
Wallet wallet1 = roundTrip(myWallet);
assertArrayEquals(myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
}
}
@Test
public void testLastBlockSeenHash() throws Exception {
// Test the lastBlockSeenHash field works.
// LastBlockSeenHash should be empty if never set.
Wallet wallet = new Wallet(params);
Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(wallet);
ByteString lastSeenBlockHash = walletProto.getLastSeenBlockHash();
assertTrue(lastSeenBlockHash.isEmpty());
// Create a block.
Block block = new Block(params, BlockTest.blockBytes);
Sha256Hash blockHash = block.getHash();
wallet.setLastBlockSeenHash(blockHash);
wallet.setLastBlockSeenHeight(1);
// Roundtrip the wallet and check it has stored the blockHash.
Wallet wallet1 = roundTrip(wallet);
assertEquals(blockHash, wallet1.getLastBlockSeenHash());
assertEquals(1, wallet1.getLastBlockSeenHeight());
// Test the Satoshi genesis block (hash of all zeroes) is roundtripped ok.
Block genesisBlock = MainNetParams.get().getGenesisBlock();
wallet.setLastBlockSeenHash(genesisBlock.getHash());
Wallet wallet2 = roundTrip(wallet);
assertEquals(genesisBlock.getHash(), wallet2.getLastBlockSeenHash());
}
@Test
public void testAppearedAtChainHeightDepthAndWorkDone() throws Exception {
// Test the TransactionConfidence appearedAtChainHeight, depth and workDone field are stored.
BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params));
final ArrayList<Transaction> txns = new ArrayList<Transaction>(2);
myWallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
txns.add(tx);
}
});
// Start by building two blocks on top of the genesis block.
Block b1 = params.getGenesisBlock().createNextBlock(myAddress);
BigInteger work1 = b1.getWork();
assertTrue(work1.signum() > 0);
Block b2 = b1.createNextBlock(myAddress);
BigInteger work2 = b2.getWork();
assertTrue(work2.signum() > 0);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
// We now have the following chain:
// genesis -> b1 -> b2
// Check the transaction confidence levels are correct before wallet roundtrip.
Threading.waitForUserCode();
assertEquals(2, txns.size());
TransactionConfidence confidence0 = txns.get(0).getConfidence();
TransactionConfidence confidence1 = txns.get(1).getConfidence();
assertEquals(1, confidence0.getAppearedAtChainHeight());
assertEquals(2, confidence1.getAppearedAtChainHeight());
assertEquals(2, confidence0.getDepthInBlocks());
assertEquals(1, confidence1.getDepthInBlocks());
// Roundtrip the wallet and check it has stored the depth and workDone.
Wallet rebornWallet = roundTrip(myWallet);
Set<Transaction> rebornTxns = rebornWallet.getTransactions(false);
assertEquals(2, rebornTxns.size());
// The transactions are not guaranteed to be in the same order so sort them to be in chain height order if required.
Iterator<Transaction> it = rebornTxns.iterator();
Transaction txA = it.next();
Transaction txB = it.next();
Transaction rebornTx0, rebornTx1;
if (txA.getConfidence().getAppearedAtChainHeight() == 1) {
rebornTx0 = txA;
rebornTx1 = txB;
} else {
rebornTx0 = txB;
rebornTx1 = txA;
}
TransactionConfidence rebornConfidence0 = rebornTx0.getConfidence();
TransactionConfidence rebornConfidence1 = rebornTx1.getConfidence();
assertEquals(1, rebornConfidence0.getAppearedAtChainHeight());
assertEquals(2, rebornConfidence1.getAppearedAtChainHeight());
assertEquals(2, rebornConfidence0.getDepthInBlocks());
assertEquals(1, rebornConfidence1.getDepthInBlocks());
}
private static Wallet roundTrip(Wallet wallet) throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
new WalletProtobufSerializer().writeWallet(wallet, output);
ByteArrayInputStream test = new ByteArrayInputStream(output.toByteArray());
assertTrue(WalletProtobufSerializer.isWallet(test));
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
return new WalletProtobufSerializer().readWallet(input);
}
@Test
public void testRoundTripNormalWallet() throws Exception {
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
}
@Test
public void testRoundTripMarriedWallet() throws Exception {
// create 2-of-2 married wallet
myWallet = new Wallet(params);
final DeterministicKeyChain partnerChain = new DeterministicKeyChain(new SecureRandom());
DeterministicKey partnerKey = DeterministicKey.deserializeB58(null, partnerChain.getWatchingKey().serializePubB58(params), params);
MarriedKeyChain chain = MarriedKeyChain.builder()
.random(new SecureRandom())
.followingKeys(partnerKey)
.threshold(2).build();
myWallet.addAndActivateHDChain(chain);
myAddress = myWallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertEquals(2, wallet1.getActiveKeychain().getSigsRequiredToSpend());
assertEquals(myAddress, wallet1.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS));
}
@Test
public void coinbaseTxns() throws Exception {
// Covers issue 420 where the outpoint index of a coinbase tx input was being mis-serialized.
Block b = params.getGenesisBlock().createNextBlockWithCoinbase(myKey.getPubKey(), FIFTY_COINS);
Transaction coinbase = b.getTransactions().get(0);
assertTrue(coinbase.isCoinBase());
BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params));
assertTrue(chain.add(b));
// Wallet now has a coinbase tx in it.
assertEquals(1, myWallet.getTransactions(true).size());
assertTrue(myWallet.getTransaction(coinbase.getHash()).isCoinBase());
Wallet wallet2 = roundTrip(myWallet);
assertEquals(1, wallet2.getTransactions(true).size());
assertTrue(wallet2.getTransaction(coinbase.getHash()).isCoinBase());
}
@Test
public void tags() throws Exception {
myWallet.setTag("foo", ByteString.copyFromUtf8("bar"));
assertEquals("bar", myWallet.getTag("foo").toStringUtf8());
myWallet = roundTrip(myWallet);
assertEquals("bar", myWallet.getTag("foo").toStringUtf8());
}
@Test
public void testExtensions() throws Exception {
myWallet.addExtension(new FooWalletExtension("com.whatever.required", true));
Protos.Wallet proto = new WalletProtobufSerializer().walletToProto(myWallet);
// Initial extension is mandatory: try to read it back into a wallet that doesn't know about it.
try {
new WalletProtobufSerializer().readWallet(params, null, proto);
fail();
} catch (UnreadableWalletException e) {
assertTrue(e.getMessage().contains("mandatory"));
}
Wallet wallet = new WalletProtobufSerializer().readWallet(params,
new WalletExtension[]{ new FooWalletExtension("com.whatever.required", true) },
proto);
assertTrue(wallet.getExtensions().containsKey("com.whatever.required"));
// Non-mandatory extensions are ignored if the wallet doesn't know how to read them.
Wallet wallet2 = new Wallet(params);
wallet2.addExtension(new FooWalletExtension("com.whatever.optional", false));
Protos.Wallet proto2 = new WalletProtobufSerializer().walletToProto(wallet2);
Wallet wallet5 = new WalletProtobufSerializer().readWallet(params, null, proto2);
assertEquals(0, wallet5.getExtensions().size());
}
@Test(expected = UnreadableWalletException.FutureVersion.class)
public void versions() throws Exception {
Protos.Wallet.Builder proto = Protos.Wallet.newBuilder(new WalletProtobufSerializer().walletToProto(myWallet));
proto.setVersion(2);
new WalletProtobufSerializer().readWallet(params, null, proto.build());
}
}

View File

@ -1,403 +0,0 @@
/*
* Copyright 2012, 2014 the original author or authors.
*
* 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.uri;
import com.dogecoin.dogecoinj.core.Address;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.TestNet3Params;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static org.junit.Assert.*;
public class BitcoinURITest {
private BitcoinURI testObject = null;
private static final String MAINNET_GOOD_ADDRESS = "1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH";
@Test
public void testConvertToBitcoinURI() throws Exception {
Address goodAddress = new Address(MainNetParams.get(), MAINNET_GOOD_ADDRESS);
// simple example
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello&message=AMessage", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("12.34"), "Hello", "AMessage"));
// example with spaces, ampersand and plus
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello%20World&message=Mess%20%26%20age%20%2B%20hope", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("12.34"), "Hello World", "Mess & age + hope"));
// no amount, label present, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?label=Hello&message=glory", BitcoinURI.convertToBitcoinURI(goodAddress, null, "Hello", "glory"));
// amount present, no label, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=0.1&message=glory", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("0.1"), null, "glory"));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=0.1&message=glory", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("0.1"), "", "glory"));
// amount present, label present, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("12.34"), "Hello", null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("12.34"), "Hello", ""));
// amount present, no label, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=1000", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("1000"), null, null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=1000", BitcoinURI.convertToBitcoinURI(goodAddress, parseCoin("1000"), "", ""));
// no amount, label present, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?label=Hello", BitcoinURI.convertToBitcoinURI(goodAddress, null, "Hello", null));
// no amount, no label, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?message=Agatha", BitcoinURI.convertToBitcoinURI(goodAddress, null, null, "Agatha"));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?message=Agatha", BitcoinURI.convertToBitcoinURI(goodAddress, null, "", "Agatha"));
// no amount, no label, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS, BitcoinURI.convertToBitcoinURI(goodAddress, null, null, null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS, BitcoinURI.convertToBitcoinURI(goodAddress, null, "", ""));
}
@Test
public void testGood_Simple() throws BitcoinURIParseException {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS);
assertNotNull(testObject);
assertNull("Unexpected amount", testObject.getAmount());
assertNull("Unexpected label", testObject.getLabel());
assertEquals("Unexpected label", 20, testObject.getAddress().getHash160().length);
}
/**
* Test a broken URI (bad scheme)
*/
@Test
public void testBad_Scheme() {
try {
testObject = new BitcoinURI(MainNetParams.get(), "blimpcoin:" + MAINNET_GOOD_ADDRESS);
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
}
}
/**
* Test a broken URI (bad syntax)
*/
@Test
public void testBad_BadSyntax() {
// Various illegal characters
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + "|" + MAINNET_GOOD_ADDRESS);
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS + "\\");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
// Separator without field
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
}
/**
* Test a broken URI (missing address)
*/
@Test
public void testBad_Address() {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME);
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
}
}
/**
* Test a broken URI (bad address type)
*/
@Test
public void testBad_IncorrectAddressType() {
try {
testObject = new BitcoinURI(TestNet3Params.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS);
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad address"));
}
}
/**
* Handles a simple amount
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Amount() throws BitcoinURIParseException {
// Test the decimal parsing
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210.12345678");
assertEquals("654321012345678", testObject.getAmount().toString());
// Test the decimal parsing
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=.12345678");
assertEquals("12345678", testObject.getAmount().toString());
// Test the integer parsing
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210");
assertEquals("654321000000000", testObject.getAmount().toString());
}
/**
* Handles a simple label
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Label() throws BitcoinURIParseException {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=Hello%20World");
assertEquals("Hello World", testObject.getLabel());
}
/**
* Handles a simple label with an embedded ampersand and plus
*
* @throws BitcoinURIParseException
* If something goes wrong
* @throws UnsupportedEncodingException
*/
@Test
public void testGood_LabelWithAmpersandAndPlus() throws Exception {
String testString = "Hello Earth & Mars + Venus";
String encodedLabel = BitcoinURI.encodeURLString(testString);
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS + "?label="
+ encodedLabel);
assertEquals(testString, testObject.getLabel());
}
/**
* Handles a Russian label (Unicode test)
*
* @throws BitcoinURIParseException
* If something goes wrong
* @throws UnsupportedEncodingException
*/
@Test
public void testGood_LabelWithRussian() throws Exception {
// Moscow in Russian in Cyrillic
String moscowString = "\u041c\u043e\u0441\u043a\u0432\u0430";
String encodedLabel = BitcoinURI.encodeURLString(moscowString);
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS + "?label="
+ encodedLabel);
assertEquals(moscowString, testObject.getLabel());
}
/**
* Handles a simple message
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Message() throws BitcoinURIParseException {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?message=Hello%20World");
assertEquals("Hello World", testObject.getMessage());
}
/**
* Handles various well-formed combinations
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Combinations() throws BitcoinURIParseException {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210&label=Hello%20World&message=Be%20well");
assertEquals(
"BitcoinURI['amount'='654321000000000','label'='Hello World','message'='Be well','address'='1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH']",
testObject.toString());
}
/**
* Handles a badly formatted amount field
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Amount() throws BitcoinURIParseException {
// Missing
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("amount"));
}
// Non-decimal (BIP 21)
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=12X4");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("amount"));
}
}
@Test
public void testEmpty_Label() throws BitcoinURIParseException {
assertNull(new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=").getLabel());
}
@Test
public void testEmpty_Message() throws BitcoinURIParseException {
assertNull(new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?message=").getMessage());
}
/**
* Handles duplicated fields (sneaky address overwrite attack)
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Duplicated() throws BitcoinURIParseException {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?address=aardvark");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("address"));
}
}
@Test
public void testGood_ManyEquals() throws BitcoinURIParseException {
assertEquals("aardvark=zebra", new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":"
+ MAINNET_GOOD_ADDRESS + "?label=aardvark=zebra").getLabel());
}
/**
* Handles unknown fields (required and not required)
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testUnknown() throws BitcoinURIParseException {
// Unknown not required field
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?aardvark=true");
assertEquals("BitcoinURI['aardvark'='true','address'='1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH']", testObject.toString());
assertEquals("true", (String) testObject.getParameterByName("aardvark"));
// Unknown not required field (isolated)
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?aardvark");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("no separator"));
}
// Unknown and required field
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?req-aardvark=true");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("req-aardvark"));
}
}
@Test
public void brokenURIs() throws BitcoinURIParseException {
// Check we can parse the incorrectly formatted URIs produced by blockchain.info and its iPhone app.
String str = "bitcoin://1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH?amount=0.01000000";
BitcoinURI uri = new BitcoinURI(str);
assertEquals("1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH", uri.getAddress().toString());
assertEquals(CENT, uri.getAmount());
}
@Test(expected = BitcoinURIParseException.class)
public void testBad_AmountTooPrecise() throws BitcoinURIParseException {
new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=0.123456789");
}
@Test(expected = BitcoinURIParseException.class)
public void testBad_NegativeAmount() throws BitcoinURIParseException {
new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=-1");
}
@Test(expected = BitcoinURIParseException.class)
public void testBad_TooLargeAmount() throws BitcoinURIParseException {
new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=100000000");
}
@Test
public void testPaymentProtocolReq() throws Exception {
// Non-backwards compatible form ...
BitcoinURI uri = new BitcoinURI(TestNet3Params.get(), "bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin%2Ff.php%3Fh%3Db0f02e7cea67f168e25ec9b9f9d584f9");
assertEquals("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9", uri.getPaymentRequestUrl());
assertEquals(ImmutableList.of("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9"),
uri.getPaymentRequestUrls());
assertNull(uri.getAddress());
}
@Test
public void testMultiplePaymentProtocolReq() throws Exception {
BitcoinURI uri = new BitcoinURI(MainNetParams.get(),
"bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin&r1=bt:112233445566");
assertEquals(ImmutableList.of("bt:112233445566", "https://bitcoincore.org/~gavin"), uri.getPaymentRequestUrls());
assertEquals("https://bitcoincore.org/~gavin", uri.getPaymentRequestUrl());
}
@Test
public void testNoPaymentProtocolReq() throws Exception {
BitcoinURI uri = new BitcoinURI(MainNetParams.get(), "bitcoin:" + MAINNET_GOOD_ADDRESS);
assertNull(uri.getPaymentRequestUrl());
assertEquals(ImmutableList.of(), uri.getPaymentRequestUrls());
assertNotNull(uri.getAddress());
}
@Test
public void testUnescapedPaymentProtocolReq() throws Exception {
BitcoinURI uri = new BitcoinURI(TestNet3Params.get(),
"bitcoin:?r=https://merchant.com/pay.php?h%3D2a8628fc2fbe");
assertEquals("https://merchant.com/pay.php?h=2a8628fc2fbe", uri.getPaymentRequestUrl());
assertEquals(ImmutableList.of("https://merchant.com/pay.php?h=2a8628fc2fbe"), uri.getPaymentRequestUrls());
assertNull(uri.getAddress());
}
}

View File

@ -1,29 +0,0 @@
package com.dogecoin.dogecoinj.utils;
import com.google.protobuf.ByteString;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BaseTaggableObjectTest {
private BaseTaggableObject obj;
@Before
public void setUp() throws Exception {
obj = new BaseTaggableObject();
}
@Test
public void tags() throws Exception {
assertNull(obj.maybeGetTag("foo"));
obj.setTag("foo", ByteString.copyFromUtf8("bar"));
assertEquals("bar", obj.getTag("foo").toStringUtf8());
}
@Test(expected = IllegalArgumentException.class)
public void exception() throws Exception {
obj.getTag("non existent");
}
}

View File

@ -1,77 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.utils;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.dogecoin.dogecoinj.core.Coin;
public class ExchangeRateTest {
@Test
public void normalRate() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("EUR", "500"));
assertEquals("0.5", rate.coinToFiat(Coin.MILLICOIN).toPlainString());
assertEquals("0.002", rate.fiatToCoin(Fiat.parseFiat("EUR", "1")).toPlainString());
}
@Test
public void bigRate() throws Exception {
ExchangeRate rate = new ExchangeRate(Coin.parseCoin("0.0001"), Fiat.parseFiat("BYR", "5320387.3"));
assertEquals("53203873000", rate.coinToFiat(Coin.COIN).toPlainString());
assertEquals("0", rate.fiatToCoin(Fiat.parseFiat("BYR", "1")).toPlainString()); // Tiny value!
}
@Test
public void smallRate() throws Exception {
ExchangeRate rate = new ExchangeRate(Coin.parseCoin("1000"), Fiat.parseFiat("XXX", "0.0001"));
assertEquals("0", rate.coinToFiat(Coin.COIN).toPlainString()); // Tiny value!
assertEquals("10000000", rate.fiatToCoin(Fiat.parseFiat("XXX", "1")).toPlainString());
}
@Test(expected = IllegalArgumentException.class)
public void currencyCodeMismatch() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("EUR", "500"));
rate.fiatToCoin(Fiat.parseFiat("USD", "1"));
}
@Test(expected = ArithmeticException.class)
public void fiatToCoinTooLarge() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1"));
rate.fiatToCoin(Fiat.parseFiat("XXX", "21000001"));
}
@Test(expected = ArithmeticException.class)
public void fiatToCoinTooSmall() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1"));
rate.fiatToCoin(Fiat.parseFiat("XXX", "-21000001"));
}
@Test(expected = ArithmeticException.class)
public void coinToFiatTooLarge() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1000000000"));
rate.coinToFiat(Coin.parseCoin("1000000"));
}
@Test(expected = ArithmeticException.class)
public void coinToFiatTooSmall() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1000000000"));
rate.coinToFiat(Coin.parseCoin("-1000000"));
}
}

View File

@ -1,81 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.utils;
import com.dogecoin.dogecoinj.core.Utils;
import org.junit.Before;
import org.junit.Test;
import java.util.PriorityQueue;
import static org.junit.Assert.assertEquals;
/**
*/
public class ExponentialBackoffTest {
private ExponentialBackoff.Params params;
private ExponentialBackoff backoff;
@Before
public void setUp() {
Utils.setMockClock(System.currentTimeMillis() / 1000);
params = new ExponentialBackoff.Params();
backoff = new ExponentialBackoff(params);
}
@Test
public void testSuccess() {
assertEquals(Utils.currentTimeMillis(), backoff.getRetryTime());
backoff.trackFailure();
backoff.trackFailure();
backoff.trackSuccess();
assertEquals(Utils.currentTimeMillis(), backoff.getRetryTime());
}
@Test
public void testFailure() {
assertEquals(Utils.currentTimeMillis(), backoff.getRetryTime());
backoff.trackFailure();
backoff.trackFailure();
backoff.trackFailure();
assertEquals(Utils.currentTimeMillis() + 121, backoff.getRetryTime());
}
@Test
public void testInQueue() {
PriorityQueue<ExponentialBackoff> queue = new PriorityQueue<ExponentialBackoff>();
ExponentialBackoff backoff1 = new ExponentialBackoff(params);
backoff.trackFailure();
backoff.trackFailure();
backoff1.trackFailure();
backoff1.trackFailure();
backoff1.trackFailure();
queue.offer(backoff);
queue.offer(backoff1);
assertEquals(queue.poll(), backoff); // The one with soonest retry time
assertEquals(queue.peek(), backoff1);
queue.offer(backoff);
assertEquals(queue.poll(), backoff); // Still the same one
}
}

View File

@ -1,59 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.utils;
import static com.dogecoin.dogecoinj.utils.Fiat.parseFiat;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class FiatTest {
@Test
public void testParseAndValueOf() {
assertEquals(Fiat.valueOf("EUR", 10000), parseFiat("EUR", "1"));
assertEquals(Fiat.valueOf("EUR", 100), parseFiat("EUR", "0.01"));
assertEquals(Fiat.valueOf("EUR", 1), parseFiat("EUR", "0.0001"));
assertEquals(Fiat.valueOf("EUR", -10000), parseFiat("EUR", "-1"));
}
@Test
public void testToFriendlyString() {
assertEquals("1.00 EUR", parseFiat("EUR", "1").toFriendlyString());
assertEquals("1.23 EUR", parseFiat("EUR", "1.23").toFriendlyString());
assertEquals("0.0010 EUR", parseFiat("EUR", "0.001").toFriendlyString());
assertEquals("-1.23 EUR", parseFiat("EUR", "-1.23").toFriendlyString());
}
@Test
public void testToPlainString() {
assertEquals("0.0015", Fiat.valueOf("EUR", 15).toPlainString());
assertEquals("1.23", parseFiat("EUR", "1.23").toPlainString());
assertEquals("0.1", parseFiat("EUR", "0.1").toPlainString());
assertEquals("1.1", parseFiat("EUR", "1.1").toPlainString());
assertEquals("21.12", parseFiat("EUR", "21.12").toPlainString());
assertEquals("321.123", parseFiat("EUR", "321.123").toPlainString());
assertEquals("4321.1234", parseFiat("EUR", "4321.1234").toPlainString());
// check there are no trailing zeros
assertEquals("1", parseFiat("EUR", "1.0").toPlainString());
assertEquals("2", parseFiat("EUR", "2.00").toPlainString());
assertEquals("3", parseFiat("EUR", "3.000").toPlainString());
assertEquals("4", parseFiat("EUR", "4.0000").toPlainString());
}
}

View File

@ -1,333 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.utils;
import static com.dogecoin.dogecoinj.core.Coin.CENT;
import static com.dogecoin.dogecoinj.core.Coin.COIN;
import static com.dogecoin.dogecoinj.core.Coin.SATOSHI;
import static com.dogecoin.dogecoinj.core.Coin.ZERO;
import static org.junit.Assert.assertEquals;
import java.util.Locale;
import org.junit.Test;
import com.dogecoin.dogecoinj.core.Coin;
public class MonetaryFormatTest {
private static final MonetaryFormat NO_CODE = MonetaryFormat.BTC.noCode();
@Test
public void testSigns() throws Exception {
assertEquals("-1.00", NO_CODE.format(Coin.COIN.negate()).toString());
assertEquals("@1.00", NO_CODE.negativeSign('@').format(Coin.COIN.negate()).toString());
assertEquals("1.00", NO_CODE.format(Coin.COIN).toString());
assertEquals("+1.00", NO_CODE.positiveSign('+').format(Coin.COIN).toString());
}
@Test
public void testDigits() throws Exception {
assertEquals("١٢.٣٤٥٦٧٨٩٠", NO_CODE.digits('\u0660').format(Coin.valueOf(1234567890l)).toString());
}
@Test
public void testDecimalMark() throws Exception {
assertEquals("1.00", NO_CODE.format(Coin.COIN).toString());
assertEquals("1,00", NO_CODE.decimalMark(',').format(Coin.COIN).toString());
}
@Test
public void testGrouping() throws Exception {
assertEquals("0.1", format(Coin.parseCoin("0.1"), 0, 1, 2, 3));
assertEquals("0.010", format(Coin.parseCoin("0.01"), 0, 1, 2, 3));
assertEquals("0.001", format(Coin.parseCoin("0.001"), 0, 1, 2, 3));
assertEquals("0.000100", format(Coin.parseCoin("0.0001"), 0, 1, 2, 3));
assertEquals("0.000010", format(Coin.parseCoin("0.00001"), 0, 1, 2, 3));
assertEquals("0.000001", format(Coin.parseCoin("0.000001"), 0, 1, 2, 3));
}
@Test
public void btcRounding() throws Exception {
assertEquals("0", format(ZERO, 0, 0));
assertEquals("0.00", format(ZERO, 0, 2));
assertEquals("1", format(COIN, 0, 0));
assertEquals("1.0", format(COIN, 0, 1));
assertEquals("1.00", format(COIN, 0, 2, 2));
assertEquals("1.00", format(COIN, 0, 2, 2, 2));
assertEquals("1.00", format(COIN, 0, 2, 2, 2, 2));
assertEquals("1.000", format(COIN, 0, 3));
assertEquals("1.0000", format(COIN, 0, 4));
final Coin justNot = COIN.subtract(SATOSHI);
assertEquals("1", format(justNot, 0, 0));
assertEquals("1.0", format(justNot, 0, 1));
assertEquals("1.00", format(justNot, 0, 2, 2));
assertEquals("1.00", format(justNot, 0, 2, 2, 2));
assertEquals("0.99999999", format(justNot, 0, 2, 2, 2, 2));
assertEquals("1.000", format(justNot, 0, 3));
assertEquals("1.0000", format(justNot, 0, 4));
final Coin slightlyMore = COIN.add(SATOSHI);
assertEquals("1", format(slightlyMore, 0, 0));
assertEquals("1.0", format(slightlyMore, 0, 1));
assertEquals("1.00", format(slightlyMore, 0, 2, 2));
assertEquals("1.00", format(slightlyMore, 0, 2, 2, 2));
assertEquals("1.00000001", format(slightlyMore, 0, 2, 2, 2, 2));
assertEquals("1.000", format(slightlyMore, 0, 3));
assertEquals("1.0000", format(slightlyMore, 0, 4));
final Coin pivot = COIN.add(SATOSHI.multiply(5));
assertEquals("1.00000005", format(pivot, 0, 8));
assertEquals("1.00000005", format(pivot, 0, 7, 1));
assertEquals("1.0000001", format(pivot, 0, 7));
final Coin value = Coin.valueOf(1122334455667788l);
assertEquals("11223345", format(value, 0, 0));
assertEquals("11223344.6", format(value, 0, 1));
assertEquals("11223344.5567", format(value, 0, 2, 2));
assertEquals("11223344.556678", format(value, 0, 2, 2, 2));
assertEquals("11223344.55667788", format(value, 0, 2, 2, 2, 2));
assertEquals("11223344.557", format(value, 0, 3));
assertEquals("11223344.5567", format(value, 0, 4));
}
@Test
public void mBtcRounding() throws Exception {
assertEquals("0", format(ZERO, 3, 0));
assertEquals("0.00", format(ZERO, 3, 2));
assertEquals("1000", format(COIN, 3, 0));
assertEquals("1000.0", format(COIN, 3, 1));
assertEquals("1000.00", format(COIN, 3, 2));
assertEquals("1000.00", format(COIN, 3, 2, 2));
assertEquals("1000.000", format(COIN, 3, 3));
assertEquals("1000.0000", format(COIN, 3, 4));
final Coin justNot = COIN.subtract(SATOSHI.multiply(10));
assertEquals("1000", format(justNot, 3, 0));
assertEquals("1000.0", format(justNot, 3, 1));
assertEquals("1000.00", format(justNot, 3, 2));
assertEquals("999.9999", format(justNot, 3, 2, 2));
assertEquals("1000.000", format(justNot, 3, 3));
assertEquals("999.9999", format(justNot, 3, 4));
final Coin slightlyMore = COIN.add(SATOSHI.multiply(10));
assertEquals("1000", format(slightlyMore, 3, 0));
assertEquals("1000.0", format(slightlyMore, 3, 1));
assertEquals("1000.00", format(slightlyMore, 3, 2));
assertEquals("1000.000", format(slightlyMore, 3, 3));
assertEquals("1000.0001", format(slightlyMore, 3, 2, 2));
assertEquals("1000.0001", format(slightlyMore, 3, 4));
final Coin pivot = COIN.add(SATOSHI.multiply(50));
assertEquals("1000.0005", format(pivot, 3, 4));
assertEquals("1000.0005", format(pivot, 3, 3, 1));
assertEquals("1000.001", format(pivot, 3, 3));
final Coin value = Coin.valueOf(1122334455667788l);
assertEquals("11223344557", format(value, 3, 0));
assertEquals("11223344556.7", format(value, 3, 1));
assertEquals("11223344556.68", format(value, 3, 2));
assertEquals("11223344556.6779", format(value, 3, 2, 2));
assertEquals("11223344556.678", format(value, 3, 3));
assertEquals("11223344556.6779", format(value, 3, 4));
}
@Test
public void uBtcRounding() throws Exception {
assertEquals("0", format(ZERO, 6, 0));
assertEquals("0.00", format(ZERO, 6, 2));
assertEquals("1000000", format(COIN, 6, 0));
assertEquals("1000000", format(COIN, 6, 0, 2));
assertEquals("1000000.0", format(COIN, 6, 1));
assertEquals("1000000.00", format(COIN, 6, 2));
final Coin justNot = COIN.subtract(SATOSHI);
assertEquals("1000000", format(justNot, 6, 0));
assertEquals("999999.99", format(justNot, 6, 0, 2));
assertEquals("1000000.0", format(justNot, 6, 1));
assertEquals("999999.99", format(justNot, 6, 2));
final Coin slightlyMore = COIN.add(SATOSHI);
assertEquals("1000000", format(slightlyMore, 6, 0));
assertEquals("1000000.01", format(slightlyMore, 6, 0, 2));
assertEquals("1000000.0", format(slightlyMore, 6, 1));
assertEquals("1000000.01", format(slightlyMore, 6, 2));
final Coin pivot = COIN.add(SATOSHI.multiply(5));
assertEquals("1000000.05", format(pivot, 6, 2));
assertEquals("1000000.05", format(pivot, 6, 0, 2));
assertEquals("1000000.1", format(pivot, 6, 1));
assertEquals("1000000.1", format(pivot, 6, 0, 1));
final Coin value = Coin.valueOf(1122334455667788l);
assertEquals("11223344556678", format(value, 6, 0));
assertEquals("11223344556677.88", format(value, 6, 2));
assertEquals("11223344556677.9", format(value, 6, 1));
assertEquals("11223344556677.88", format(value, 6, 2));
}
private String format(Coin coin, int shift, int minDecimals, int... decimalGroups) {
return NO_CODE.shift(shift).minDecimals(minDecimals).optionalDecimals(decimalGroups).format(coin).toString();
}
@Test
public void repeatOptionalDecimals() {
assertEquals("0.00000001", formatRepeat(SATOSHI, 2, 4));
assertEquals("0.00000010", formatRepeat(SATOSHI.multiply(10), 2, 4));
assertEquals("0.01", formatRepeat(CENT, 2, 4));
assertEquals("0.10", formatRepeat(CENT.multiply(10), 2, 4));
assertEquals("0", formatRepeat(SATOSHI, 2, 2));
assertEquals("0", formatRepeat(SATOSHI.multiply(10), 2, 2));
assertEquals("0.01", formatRepeat(CENT, 2, 2));
assertEquals("0.10", formatRepeat(CENT.multiply(10), 2, 2));
assertEquals("0", formatRepeat(CENT, 2, 0));
assertEquals("0", formatRepeat(CENT.multiply(10), 2, 0));
}
private String formatRepeat(Coin coin, int decimals, int repetitions) {
return NO_CODE.minDecimals(0).repeatOptionalDecimals(decimals, repetitions).format(coin).toString();
}
@Test
public void standardCodes() throws Exception {
assertEquals("DOGE 0.00", MonetaryFormat.BTC.format(Coin.ZERO).toString());
assertEquals("mDOGE 0.00", MonetaryFormat.MBTC.format(Coin.ZERO).toString());
assertEquals("µDOGE 0", MonetaryFormat.UBTC.format(Coin.ZERO).toString());
}
@Test
public void customCode() throws Exception {
assertEquals("dDOGE 0", MonetaryFormat.UBTC.code(1, "dDOGE").shift(1).format(Coin.ZERO).toString());
}
@Test
public void codeOrientation() throws Exception {
assertEquals("DOGE 0.00", MonetaryFormat.BTC.prefixCode().format(Coin.ZERO).toString());
assertEquals("0.00 DOGE", MonetaryFormat.BTC.postfixCode().format(Coin.ZERO).toString());
}
@Test
public void codeSeparator() throws Exception {
assertEquals("DOGE@0.00", MonetaryFormat.BTC.codeSeparator('@').format(Coin.ZERO).toString());
}
@Test(expected = NumberFormatException.class)
public void missingCode() throws Exception {
MonetaryFormat.UBTC.shift(1).format(Coin.ZERO);
}
@Test
public void withLocale() throws Exception {
final Coin value = Coin.valueOf(-1234567890l);
assertEquals("-12.34567890", NO_CODE.withLocale(Locale.US).format(value).toString());
assertEquals("-12,34567890", NO_CODE.withLocale(Locale.GERMANY).format(value).toString());
assertEquals("-१२.३४५६७८९०", NO_CODE.withLocale(new Locale("hi", "IN")).format(value).toString()); // Devanagari
}
@Test
public void parse() throws Exception {
assertEquals(Coin.COIN, NO_CODE.parse("1"));
assertEquals(Coin.COIN, NO_CODE.parse("1."));
assertEquals(Coin.COIN, NO_CODE.parse("1.0"));
assertEquals(Coin.COIN, NO_CODE.decimalMark(',').parse("1,0"));
assertEquals(Coin.COIN, NO_CODE.parse("01.0000000000"));
assertEquals(Coin.COIN, NO_CODE.positiveSign('+').parse("+1.0"));
assertEquals(Coin.COIN.negate(), NO_CODE.parse("-1"));
assertEquals(Coin.COIN.negate(), NO_CODE.parse("-1.0"));
assertEquals(Coin.CENT, NO_CODE.parse(".01"));
assertEquals(Coin.MILLICOIN, MonetaryFormat.MBTC.parse("1"));
assertEquals(Coin.MILLICOIN, MonetaryFormat.MBTC.parse("1.0"));
assertEquals(Coin.MILLICOIN, MonetaryFormat.MBTC.parse("01.0000000000"));
assertEquals(Coin.MILLICOIN, MonetaryFormat.MBTC.positiveSign('+').parse("+1.0"));
assertEquals(Coin.MILLICOIN.negate(), MonetaryFormat.MBTC.parse("-1"));
assertEquals(Coin.MILLICOIN.negate(), MonetaryFormat.MBTC.parse("-1.0"));
assertEquals(Coin.MICROCOIN, MonetaryFormat.UBTC.parse("1"));
assertEquals(Coin.MICROCOIN, MonetaryFormat.UBTC.parse("1.0"));
assertEquals(Coin.MICROCOIN, MonetaryFormat.UBTC.parse("01.0000000000"));
assertEquals(Coin.MICROCOIN, MonetaryFormat.UBTC.positiveSign('+').parse("+1.0"));
assertEquals(Coin.MICROCOIN.negate(), MonetaryFormat.UBTC.parse("-1"));
assertEquals(Coin.MICROCOIN.negate(), MonetaryFormat.UBTC.parse("-1.0"));
assertEquals(Coin.CENT, NO_CODE.withLocale(new Locale("hi", "IN")).parse(".०१")); // Devanagari
}
@Test(expected = NumberFormatException.class)
public void parseInvalidEmpty() throws Exception {
NO_CODE.parse("");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidWhitespaceBefore() throws Exception {
NO_CODE.parse(" 1");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidWhitespaceSign() throws Exception {
NO_CODE.parse("- 1");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidWhitespaceAfter() throws Exception {
NO_CODE.parse("1 ");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidMultipleDecimalMarks() throws Exception {
NO_CODE.parse("1.0.0");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidDecimalMark() throws Exception {
NO_CODE.decimalMark(',').parse("1.0");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidPositiveSign() throws Exception {
NO_CODE.positiveSign('@').parse("+1.0");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidNegativeSign() throws Exception {
NO_CODE.negativeSign('@').parse("-1.0");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidHugeNumber() throws Exception {
NO_CODE.parse("99999999999999999999");
}
@Test(expected = NumberFormatException.class)
public void parseInvalidHugeNegativeNumber() throws Exception {
NO_CODE.parse("-99999999999999999999");
}
private static final Fiat ONE_EURO = Fiat.parseFiat("EUR", "1");
@Test
public void fiat() throws Exception {
assertEquals(ONE_EURO, NO_CODE.parseFiat("EUR", "1"));
}
}

View File

@ -1,274 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.wallet;
import com.dogecoin.dogecoinj.core.BloomFilter;
import com.dogecoin.dogecoinj.core.ECKey;
import com.dogecoin.dogecoinj.core.Utils;
import com.dogecoin.dogecoinj.crypto.KeyCrypter;
import com.dogecoin.dogecoinj.crypto.KeyCrypterException;
import com.dogecoin.dogecoinj.crypto.KeyCrypterScrypt;
import com.dogecoin.dogecoinj.store.UnreadableWalletException;
import com.dogecoin.dogecoinj.utils.Threading;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class BasicKeyChainTest {
private BasicKeyChain chain;
private AtomicReference<List<ECKey>> onKeysAdded;
private AtomicBoolean onKeysAddedRan;
@Before
public void setup() {
chain = new BasicKeyChain();
onKeysAdded = new AtomicReference<List<ECKey>>();
onKeysAddedRan = new AtomicBoolean();
chain.addEventListener(new AbstractKeyChainEventListener() {
@Override
public void onKeysAdded(List<ECKey> keys2) {
onKeysAdded.set(keys2);
onKeysAddedRan.set(true);
}
}, Threading.SAME_THREAD);
}
@Test
public void importKeys() {
long now = Utils.currentTimeSeconds();
Utils.setMockClock(now);
final ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
final ECKey key2 = new ECKey();
final ArrayList<ECKey> keys = Lists.newArrayList(key1, key2);
// Import two keys, check the event is correct.
assertEquals(2, chain.importKeys(keys));
assertEquals(2, chain.numKeys());
assertTrue(onKeysAddedRan.getAndSet(false));
assertArrayEquals(keys.toArray(), onKeysAdded.get().toArray());
assertEquals(now, chain.getEarliestKeyCreationTime());
// Check we ignore duplicates.
final ECKey newKey = new ECKey();
keys.add(newKey);
assertEquals(1, chain.importKeys(keys));
assertTrue(onKeysAddedRan.getAndSet(false));
assertEquals(newKey, onKeysAdded.getAndSet(null).get(0));
assertEquals(0, chain.importKeys(keys));
assertFalse(onKeysAddedRan.getAndSet(false));
assertNull(onKeysAdded.get());
assertTrue(chain.hasKey(key1));
assertTrue(chain.hasKey(key2));
assertEquals(key1, chain.findKeyFromPubHash(key1.getPubKeyHash()));
assertEquals(key2, chain.findKeyFromPubKey(key2.getPubKey()));
assertNull(chain.findKeyFromPubKey(key2.getPubKeyHash()));
}
@Test
public void removeKey() {
ECKey key = new ECKey();
chain.importKeys(key);
assertEquals(1, chain.numKeys());
assertTrue(chain.removeKey(key));
assertEquals(0, chain.numKeys());
assertFalse(chain.removeKey(key));
}
@Test
public void getKey() {
ECKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(onKeysAddedRan.getAndSet(false));
assertEquals(key1, onKeysAdded.getAndSet(null).get(0));
ECKey key2 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertFalse(onKeysAddedRan.getAndSet(false));
assertEquals(key2, key1);
}
@Test(expected = IllegalStateException.class)
public void checkPasswordNoKeys() {
chain.checkPassword("test");
}
@Test(expected = IllegalStateException.class)
public void checkPasswordNotEncrypted() {
final ArrayList<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey());
chain.importKeys(keys);
chain.checkPassword("test");
}
@Test(expected = IllegalStateException.class)
public void doubleEncryptFails() {
final ArrayList<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey());
chain.importKeys(keys);
chain = chain.toEncrypted("foo");
chain.toEncrypted("foo");
}
@Test
public void encryptDecrypt() {
final ECKey key1 = new ECKey();
chain.importKeys(key1, new ECKey());
final String PASSWORD = "foobar";
chain = chain.toEncrypted(PASSWORD);
final KeyCrypter keyCrypter = chain.getKeyCrypter();
assertNotNull(keyCrypter);
assertTrue(keyCrypter instanceof KeyCrypterScrypt);
assertTrue(chain.checkPassword(PASSWORD));
assertFalse(chain.checkPassword("wrong"));
ECKey key = chain.findKeyFromPubKey(key1.getPubKey());
assertTrue(key.isEncrypted());
assertNull(key.getSecretBytes());
try {
// Don't allow import of an unencrypted key.
chain.importKeys(new ECKey());
fail();
} catch (KeyCrypterException e) {
}
try {
chain.toDecrypted(keyCrypter.deriveKey("wrong"));
fail();
} catch (KeyCrypterException e) {}
chain = chain.toDecrypted(PASSWORD);
key = chain.findKeyFromPubKey(key1.getPubKey());
assertFalse(key.isEncrypted());
key.getPrivKeyBytes();
}
@Test(expected = KeyCrypterException.class)
public void cannotImportEncryptedKey() {
final ECKey key1 = new ECKey();
chain.importKeys(ImmutableList.of(key1));
chain = chain.toEncrypted("foobar");
ECKey encryptedKey = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(encryptedKey.isEncrypted());
BasicKeyChain chain2 = new BasicKeyChain();
chain2.importKeys(ImmutableList.of(encryptedKey));
}
@Test(expected = KeyCrypterException.class)
public void cannotMixParams() throws Exception {
chain = chain.toEncrypted("foobar");
KeyCrypterScrypt scrypter = new KeyCrypterScrypt(2); // Some bogus params.
ECKey key1 = new ECKey().encrypt(scrypter, scrypter.deriveKey("other stuff"));
chain.importKeys(key1);
}
@Test
public void serializationUnencrypted() throws UnreadableWalletException {
Utils.setMockClock();
Date now = Utils.now();
final ECKey key1 = new ECKey();
Utils.rollMockClock(5000);
final ECKey key2 = new ECKey();
chain.importKeys(ImmutableList.of(key1, key2));
List<Protos.Key> keys = chain.serializeToProtobuf();
assertEquals(2, keys.size());
assertArrayEquals(key1.getPubKey(), keys.get(0).getPublicKey().toByteArray());
assertArrayEquals(key2.getPubKey(), keys.get(1).getPublicKey().toByteArray());
assertArrayEquals(key1.getPrivKeyBytes(), keys.get(0).getSecretBytes().toByteArray());
assertArrayEquals(key2.getPrivKeyBytes(), keys.get(1).getSecretBytes().toByteArray());
long normTime = (long) (Math.floor(now.getTime() / 1000) * 1000);
assertEquals(normTime, keys.get(0).getCreationTimestamp());
assertEquals(normTime + 5000 * 1000, keys.get(1).getCreationTimestamp());
chain = BasicKeyChain.fromProtobufUnencrypted(keys);
assertEquals(2, chain.getKeys().size());
assertEquals(key1, chain.getKeys().get(0));
assertEquals(key2, chain.getKeys().get(1));
}
@Test
public void serializationEncrypted() throws UnreadableWalletException {
ECKey key1 = new ECKey();
chain.importKeys(key1);
chain = chain.toEncrypted("foo bar");
key1 = chain.getKeys().get(0);
List<Protos.Key> keys = chain.serializeToProtobuf();
assertEquals(1, keys.size());
assertArrayEquals(key1.getPubKey(), keys.get(0).getPublicKey().toByteArray());
assertFalse(keys.get(0).hasSecretBytes());
assertTrue(keys.get(0).hasEncryptedData());
chain = BasicKeyChain.fromProtobufEncrypted(keys, checkNotNull(chain.getKeyCrypter()));
assertEquals(key1.getEncryptedPrivateKey(), chain.getKeys().get(0).getEncryptedPrivateKey());
assertTrue(chain.checkPassword("foo bar"));
}
@Test
public void watching() throws UnreadableWalletException {
ECKey key1 = new ECKey();
ECKey pub = ECKey.fromPublicOnly(key1.getPubKeyPoint());
chain.importKeys(pub);
assertEquals(1, chain.numKeys());
List<Protos.Key> keys = chain.serializeToProtobuf();
assertEquals(1, keys.size());
assertTrue(keys.get(0).hasPublicKey());
assertFalse(keys.get(0).hasSecretBytes());
chain = BasicKeyChain.fromProtobufUnencrypted(keys);
assertEquals(1, chain.numKeys());
assertFalse(chain.findKeyFromPubKey(pub.getPubKey()).hasPrivKey());
}
@Test
public void bloom() throws Exception {
ECKey key1 = new ECKey();
ECKey key2 = new ECKey();
chain.importKeys(key1, key2);
assertEquals(2, chain.numKeys());
assertEquals(4, chain.numBloomFilterEntries());
BloomFilter filter = chain.getFilter(4, 0.001, 100);
assertTrue(filter.contains(key1.getPubKey()));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key2.getPubKey()));
assertTrue(filter.contains(key2.getPubKeyHash()));
ECKey key3 = new ECKey();
assertFalse(filter.contains(key3.getPubKey()));
}
@Test
public void keysBeforeAndAfter() throws Exception {
Utils.setMockClock();
long now = Utils.currentTimeSeconds();
final ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
final ECKey key2 = new ECKey();
final List<ECKey> keys = Lists.newArrayList(key1, key2);
assertEquals(2, chain.importKeys(keys));
assertNull(chain.findOldestKeyAfter(now + 86400 * 2));
assertEquals(key1, chain.findOldestKeyAfter(now - 1));
assertEquals(key2, chain.findOldestKeyAfter(now + 86400 - 1));
assertEquals(2, chain.findKeysBefore(now + 86400 * 2).size());
assertEquals(1, chain.findKeysBefore(now + 1).size());
assertEquals(0, chain.findKeysBefore(now - 1).size());
}
}

View File

@ -1,133 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.wallet;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.params.RegTestParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.testing.FakeTxBuilder;
import com.dogecoin.dogecoinj.testing.TestWithWallet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class DefaultCoinSelectorTest extends TestWithWallet {
private static final NetworkParameters params = UnitTestParams.get();
@Before
@Override
public void setUp() throws Exception {
super.setUp();
Utils.setMockClock(); // Use mock clock
}
@After
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void selectable() throws Exception {
Transaction t;
t = new Transaction(params);
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.PENDING);
assertFalse(DefaultCoinSelector.isSelectable(t));
t.getConfidence().setSource(TransactionConfidence.Source.SELF);
assertFalse(DefaultCoinSelector.isSelectable(t));
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("1.2.3.4")));
assertFalse(DefaultCoinSelector.isSelectable(t));
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("5.6.7.8")));
assertTrue(DefaultCoinSelector.isSelectable(t));
t = new Transaction(params);
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.BUILDING);
assertTrue(DefaultCoinSelector.isSelectable(t));
t = new Transaction(RegTestParams.get());
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.PENDING);
t.getConfidence().setSource(TransactionConfidence.Source.SELF);
assertTrue(DefaultCoinSelector.isSelectable(t));
}
@Test
public void depthOrdering() throws Exception {
// Send two transactions in two blocks on top of each other.
Transaction t1 = checkNotNull(sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
Transaction t2 = checkNotNull(sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
// Check we selected just the oldest one.
DefaultCoinSelector selector = new DefaultCoinSelector();
CoinSelection selection = selector.select(COIN, wallet.calculateAllSpendCandidates(true));
assertTrue(selection.gathered.contains(t1.getOutputs().get(0)));
assertEquals(COIN, selection.valueGathered);
// Check we ordered them correctly (by depth).
ArrayList<TransactionOutput> candidates = new ArrayList<TransactionOutput>();
candidates.add(t2.getOutput(0));
candidates.add(t1.getOutput(0));
DefaultCoinSelector.sortOutputs(candidates);
assertEquals(t1.getOutput(0), candidates.get(0));
assertEquals(t2.getOutput(0), candidates.get(1));
}
@Test
public void coinAgeOrdering() throws Exception {
// Send three transactions in four blocks on top of each other. Coin age of t1 is 1*4=4, coin age of t2 = 2*2=4
// and t3=0.01.
Transaction t1 = checkNotNull(sendMoneyToWallet(COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
// Padding block.
wallet.notifyNewBestBlock(FakeTxBuilder.createFakeBlock(blockStore).storedBlock);
final Coin TWO_COINS = COIN.multiply(2);
Transaction t2 = checkNotNull(sendMoneyToWallet(TWO_COINS, AbstractBlockChain.NewBlockType.BEST_CHAIN));
Transaction t3 = checkNotNull(sendMoneyToWallet(CENT, AbstractBlockChain.NewBlockType.BEST_CHAIN));
// Should be ordered t2, t1, t3.
ArrayList<TransactionOutput> candidates = new ArrayList<TransactionOutput>();
candidates.add(t3.getOutput(0));
candidates.add(t2.getOutput(0));
candidates.add(t1.getOutput(0));
DefaultCoinSelector.sortOutputs(candidates);
assertEquals(t2.getOutput(0), candidates.get(0));
assertEquals(t1.getOutput(0), candidates.get(1));
assertEquals(t3.getOutput(0), candidates.get(2));
}
@Test
public void identicalInputs() throws Exception {
// Add four outputs to a transaction with same value and destination. Select them all.
Transaction t = new Transaction(params);
java.util.List<TransactionOutput> outputs = Arrays.asList(
new TransactionOutput(params, t, Coin.valueOf(30302787), myAddress),
new TransactionOutput(params, t, Coin.valueOf(30302787), myAddress),
new TransactionOutput(params, t, Coin.valueOf(30302787), myAddress),
new TransactionOutput(params, t, Coin.valueOf(30302787), myAddress)
);
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.BUILDING);
DefaultCoinSelector selector = new DefaultCoinSelector();
CoinSelection selection = selector.select(COIN.multiply(2), outputs);
assertTrue(selection.gathered.size() == 4);
}
}

View File

@ -1,202 +0,0 @@
/*
* 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 com.dogecoin.dogecoinj.wallet;
import java.util.Arrays;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.TransactionSignature;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.script.Script;
import com.dogecoin.dogecoinj.script.ScriptBuilder;
import com.dogecoin.dogecoinj.script.ScriptChunk;
import com.google.common.collect.ImmutableList;
import com.dogecoin.dogecoinj.wallet.DefaultRiskAnalysis.RuleViolation;
import org.junit.Before;
import org.junit.Test;
import static com.dogecoin.dogecoinj.core.Coin.COIN;
import static com.dogecoin.dogecoinj.script.ScriptOpCodes.OP_PUSHDATA1;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
public class DefaultRiskAnalysisTest {
// Uses mainnet because isStandard checks are disabled on testnet.
private static final NetworkParameters params = MainNetParams.get();
private Wallet wallet;
private final int TIMESTAMP = 1384190189;
private static final ECKey key1 = new ECKey();
private final ImmutableList<Transaction> NO_DEPS = ImmutableList.of();
@Before
public void setup() {
wallet = new Wallet(params) {
@Override
public int getLastBlockSeenHeight() {
return 1000;
}
@Override
public long getLastBlockSeenTimeSecs() {
return TIMESTAMP;
}
};
}
@Test
public void nonFinal() throws Exception {
// Verify that just having a lock time in the future is not enough to be considered risky (it's still final).
Transaction tx = new Transaction(params);
TransactionInput input = tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
tx.addOutput(COIN, key1);
tx.setLockTime(TIMESTAMP + 86400);
{
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
assertNull(analysis.getNonFinal());
// Verify we can't re-use a used up risk analysis.
try {
analysis.analyze();
fail();
} catch (IllegalStateException e) {}
}
// Set a sequence number on the input to make it genuinely non-final. Verify it's risky.
input.setSequenceNumber(1);
{
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
assertEquals(tx, analysis.getNonFinal());
}
// If the lock time is the current block, it's about to become final and we consider it non-risky.
tx.setLockTime(1000);
{
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
}
}
@Test
public void selfCreatedAreNotRisky() {
Transaction tx = new Transaction(params);
tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0)).setSequenceNumber(1);
tx.addOutput(COIN, key1);
tx.setLockTime(TIMESTAMP + 86400);
{
// Is risky ...
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
}
tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
{
// Is no longer risky.
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
}
}
@Test
public void nonFinalDependency() {
// Final tx has a dependency that is non-final.
Transaction tx1 = new Transaction(params);
tx1.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0)).setSequenceNumber(1);
TransactionOutput output = tx1.addOutput(COIN, key1);
tx1.setLockTime(TIMESTAMP + 86400);
Transaction tx2 = new Transaction(params);
tx2.addInput(output);
tx2.addOutput(COIN, new ECKey());
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx2, ImmutableList.of(tx1));
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
assertEquals(tx1, analysis.getNonFinal());
}
@Test
public void nonStandardDust() {
Transaction standardTx = new Transaction(params);
standardTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
standardTx.addOutput(COIN, key1);
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, standardTx, NO_DEPS).analyze());
Transaction dustTx = new Transaction(params);
dustTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
dustTx.addOutput(Coin.SATOSHI, key1); // 1 Satoshi
assertEquals(RiskAnalysis.Result.NON_STANDARD, DefaultRiskAnalysis.FACTORY.create(wallet, dustTx, NO_DEPS).analyze());
Transaction edgeCaseTx = new Transaction(params);
edgeCaseTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
edgeCaseTx.addOutput(DefaultRiskAnalysis.MIN_ANALYSIS_NONDUST_OUTPUT, key1); // Dust threshold
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, edgeCaseTx, NO_DEPS).analyze());
}
@Test
public void nonShortestPossiblePushData() {
ScriptChunk nonStandardChunk = new ScriptChunk(OP_PUSHDATA1, new byte[75]);
byte[] nonStandardScript = new ScriptBuilder().addChunk(nonStandardChunk).build().getProgram();
// Test non-standard script as an input.
Transaction tx = new Transaction(params);
assertEquals(DefaultRiskAnalysis.RuleViolation.NONE, DefaultRiskAnalysis.isStandard(tx));
tx.addInput(new TransactionInput(params, null, nonStandardScript));
assertEquals(DefaultRiskAnalysis.RuleViolation.SHORTEST_POSSIBLE_PUSHDATA, DefaultRiskAnalysis.isStandard(tx));
// Test non-standard script as an output.
tx.clearInputs();
assertEquals(DefaultRiskAnalysis.RuleViolation.NONE, DefaultRiskAnalysis.isStandard(tx));
tx.addOutput(new TransactionOutput(params, null, COIN, nonStandardScript));
assertEquals(DefaultRiskAnalysis.RuleViolation.SHORTEST_POSSIBLE_PUSHDATA, DefaultRiskAnalysis.isStandard(tx));
}
@Test
public void canonicalSignature() {
TransactionSignature sig = TransactionSignature.dummy();
Script scriptOk = ScriptBuilder.createInputScript(sig);
assertEquals(RuleViolation.NONE,
DefaultRiskAnalysis.isInputStandard(new TransactionInput(params, null, scriptOk.getProgram())));
byte[] sigBytes = sig.encodeToBitcoin();
// Appending a zero byte makes the signature uncanonical without violating DER encoding.
Script scriptUncanonicalEncoding = new ScriptBuilder().data(Arrays.copyOf(sigBytes, sigBytes.length + 1))
.build();
assertEquals(RuleViolation.SIGNATURE_CANONICAL_ENCODING,
DefaultRiskAnalysis.isInputStandard(new TransactionInput(params, null, scriptUncanonicalEncoding
.getProgram())));
}
@Test
public void standardOutputs() throws Exception {
Transaction tx = new Transaction(params);
tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
// A pay to address output
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1.toAddress(params)));
// A pay to pubkey output
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1));
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1));
// 1-of-2 multisig output.
ImmutableList<ECKey> keys = ImmutableList.of(key1, new ECKey());
tx.addOutput(Coin.CENT, ScriptBuilder.createMultiSigOutputScript(1, keys));
// 2-of-2 multisig output.
tx.addOutput(Coin.CENT, ScriptBuilder.createMultiSigOutputScript(2, keys));
// P2SH
tx.addOutput(Coin.CENT, ScriptBuilder.createP2SHOutputScript(1, keys));
// OP_RETURN
tx.addOutput(Coin.CENT, ScriptBuilder.createOpReturnScript("hi there".getBytes()));
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS).analyze());
}
}

View File

@ -1,327 +0,0 @@
/**
* 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 com.dogecoin.dogecoinj.wallet;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.DeterministicHierarchy;
import com.dogecoin.dogecoinj.crypto.DeterministicKey;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.params.UnitTestParams;
import com.dogecoin.dogecoinj.store.UnreadableWalletException;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.utils.Threading;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class DeterministicKeyChainTest {
private DeterministicKeyChain chain;
private final byte[] ENTROPY = Sha256Hash.create("don't use a string seed like this in real life".getBytes()).getBytes();
@Before
public void setup() {
BriefLogFormatter.init();
// You should use a random seed instead. The secs constant comes from the unit test file, so we can compare
// serialized data properly.
long secs = 1389353062L;
chain = new DeterministicKeyChain(ENTROPY, "", secs);
chain.setLookaheadSize(10);
assertEquals(secs, checkNotNull(chain.getSeed()).getCreationTimeSeconds());
}
@Test
public void derive() throws Exception {
ECKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
ECKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
final Address address = new Address(UnitTestParams.get(), "n1bQNoEx8uhmCzzA5JPG6sFdtsUQhwiQJV");
assertEquals(address, key1.toAddress(UnitTestParams.get()));
assertEquals("mnHUcqUVvrfi5kAaXJDQzBb9HsWs78b42R", key2.toAddress(UnitTestParams.get()).toString());
assertEquals(key1, chain.findKeyFromPubHash(address.getHash160()));
assertEquals(key2, chain.findKeyFromPubKey(key2.getPubKey()));
key1.sign(Sha256Hash.ZERO_HASH);
ECKey key3 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals("mqumHgVDqNzuXNrszBmi7A2UpmwaPMx4HQ", key3.toAddress(UnitTestParams.get()).toString());
key3.sign(Sha256Hash.ZERO_HASH);
}
@Test
public void getKeys() throws Exception {
chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain.getKey(KeyChain.KeyPurpose.CHANGE);
chain.maybeLookAhead();
assertEquals(2, chain.getKeys(false).size());
}
@Test
public void signMessage() throws Exception {
ECKey key = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
key.verifyMessage("test", key.signMessage("test"));
}
@Test
public void events() throws Exception {
// Check that we get the right events at the right time.
final List<List<ECKey>> listenerKeys = Lists.newArrayList();
long secs = 1389353062L;
chain = new DeterministicKeyChain(ENTROPY, "", secs);
chain.addEventListener(new AbstractKeyChainEventListener() {
@Override
public void onKeysAdded(List<ECKey> keys) {
listenerKeys.add(keys);
}
}, Threading.SAME_THREAD);
assertEquals(0, listenerKeys.size());
chain.setLookaheadSize(5);
assertEquals(0, listenerKeys.size());
ECKey key = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(1, listenerKeys.size()); // 1 event
final List<ECKey> firstEvent = listenerKeys.get(0);
assertEquals(1, firstEvent.size());
assertTrue(firstEvent.contains(key)); // order is not specified.
listenerKeys.clear();
chain.maybeLookAhead();
final List<ECKey> secondEvent = listenerKeys.get(0);
assertEquals(12, secondEvent.size()); // (5 lookahead keys, +1 lookahead threshold) * 2 chains
listenerKeys.clear();
chain.getKey(KeyChain.KeyPurpose.CHANGE);
// At this point we've entered the threshold zone so more keys won't immediately trigger more generations.
assertEquals(0, listenerKeys.size()); // 1 event
final int lookaheadThreshold = chain.getLookaheadThreshold() + chain.getLookaheadSize();
for (int i = 0; i < lookaheadThreshold; i++)
chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(1, listenerKeys.size()); // 1 event
assertEquals(1, listenerKeys.get(0).size()); // 1 key.
}
@Test
public void random() {
// Can't test much here but verify the constructor worked and the class is functional. The other tests rely on
// a fixed seed to be deterministic.
chain = new DeterministicKeyChain(new SecureRandom(), 384);
chain.setLookaheadSize(10);
chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).sign(Sha256Hash.ZERO_HASH);
chain.getKey(KeyChain.KeyPurpose.CHANGE).sign(Sha256Hash.ZERO_HASH);
}
@Test
public void serializeUnencrypted() throws UnreadableWalletException {
chain.maybeLookAhead();
DeterministicKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key3 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
List<Protos.Key> keys = chain.serializeToProtobuf();
// 1 mnemonic/seed, 1 master key, 1 account key, 2 internal keys, 3 derived, 20 lookahead and 5 lookahead threshold.
int numItems =
1 // mnemonic/seed
+ 1 // master key
+ 1 // account key
+ 2 // ext/int parent keys
+ (chain.getLookaheadSize() + chain.getLookaheadThreshold()) * 2 // lookahead zone on each chain
;
assertEquals(numItems, keys.size());
// Get another key that will be lost during round-tripping, to ensure we can derive it again.
DeterministicKey key4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
final String EXPECTED_SERIALIZATION = checkSerialization(keys, "deterministic-wallet-serialization.txt");
// Round trip the data back and forth to check it is preserved.
int oldLookaheadSize = chain.getLookaheadSize();
chain = DeterministicKeyChain.fromProtobuf(keys, null).get(0);
assertEquals(EXPECTED_SERIALIZATION, protoToString(chain.serializeToProtobuf()));
assertEquals(key1, chain.findKeyFromPubHash(key1.getPubKeyHash()));
assertEquals(key2, chain.findKeyFromPubHash(key2.getPubKeyHash()));
assertEquals(key3, chain.findKeyFromPubHash(key3.getPubKeyHash()));
assertEquals(key4, chain.getKey(KeyChain.KeyPurpose.CHANGE));
key1.sign(Sha256Hash.ZERO_HASH);
key2.sign(Sha256Hash.ZERO_HASH);
key3.sign(Sha256Hash.ZERO_HASH);
key4.sign(Sha256Hash.ZERO_HASH);
assertEquals(oldLookaheadSize, chain.getLookaheadSize());
}
@Test(expected = IllegalStateException.class)
public void notEncrypted() {
chain.toDecrypted("fail");
}
@Test(expected = IllegalStateException.class)
public void encryptTwice() {
chain = chain.toEncrypted("once");
chain = chain.toEncrypted("twice");
}
private void checkEncryptedKeyChain(DeterministicKeyChain encChain, DeterministicKey key1) {
// Check we can look keys up and extend the chain without the AES key being provided.
DeterministicKey encKey1 = encChain.findKeyFromPubKey(key1.getPubKey());
DeterministicKey encKey2 = encChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertFalse(key1.isEncrypted());
assertTrue(encKey1.isEncrypted());
assertEquals(encKey1.getPubKeyPoint(), key1.getPubKeyPoint());
final KeyParameter aesKey = checkNotNull(encChain.getKeyCrypter()).deriveKey("open secret");
encKey1.sign(Sha256Hash.ZERO_HASH, aesKey);
encKey2.sign(Sha256Hash.ZERO_HASH, aesKey);
assertTrue(encChain.checkAESKey(aesKey));
assertFalse(encChain.checkPassword("access denied"));
assertTrue(encChain.checkPassword("open secret"));
}
@Test
public void encryption() throws UnreadableWalletException {
DeterministicKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKeyChain encChain = chain.toEncrypted("open secret");
DeterministicKey encKey1 = encChain.findKeyFromPubKey(key1.getPubKey());
checkEncryptedKeyChain(encChain, key1);
// Round-trip to ensure de/serialization works and that we can store two chains and they both deserialize.
List<Protos.Key> serialized = encChain.serializeToProtobuf();
List<Protos.Key> doubled = Lists.newArrayListWithExpectedSize(serialized.size() * 2);
doubled.addAll(serialized);
doubled.addAll(serialized);
final List<DeterministicKeyChain> chains = DeterministicKeyChain.fromProtobuf(doubled, encChain.getKeyCrypter());
assertEquals(2, chains.size());
encChain = chains.get(0);
checkEncryptedKeyChain(encChain, chain.findKeyFromPubKey(key1.getPubKey()));
encChain = chains.get(1);
checkEncryptedKeyChain(encChain, chain.findKeyFromPubKey(key1.getPubKey()));
DeterministicKey encKey2 = encChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
// Decrypt and check the keys match.
DeterministicKeyChain decChain = encChain.toDecrypted("open secret");
DeterministicKey decKey1 = decChain.findKeyFromPubHash(encKey1.getPubKeyHash());
DeterministicKey decKey2 = decChain.findKeyFromPubHash(encKey2.getPubKeyHash());
assertEquals(decKey1.getPubKeyPoint(), encKey1.getPubKeyPoint());
assertEquals(decKey2.getPubKeyPoint(), encKey2.getPubKeyPoint());
assertFalse(decKey1.isEncrypted());
assertFalse(decKey2.isEncrypted());
assertNotEquals(encKey1.getParent(), decKey1.getParent()); // parts of a different hierarchy
// Check we can once again derive keys from the decrypted chain.
decChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).sign(Sha256Hash.ZERO_HASH);
decChain.getKey(KeyChain.KeyPurpose.CHANGE).sign(Sha256Hash.ZERO_HASH);
}
@Test
public void watchingChain() throws UnreadableWalletException {
Utils.setMockClock();
DeterministicKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key3 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
DeterministicKey key4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
NetworkParameters params = MainNetParams.get();
DeterministicKey watchingKey = chain.getWatchingKey();
final String pub58 = watchingKey.serializePubB58(params);
assertEquals("xpub69KR9epSNBM59KLuasxMU5CyKytMJjBP5HEZ5p8YoGUCpM6cM9hqxB9DDPCpUUtqmw5duTckvPfwpoWGQUFPmRLpxs5jYiTf2u6xRMcdhDf", pub58);
watchingKey = DeterministicKey.deserializeB58(null, pub58, params);
watchingKey.setCreationTimeSeconds(100000);
chain = DeterministicKeyChain.watch(watchingKey);
assertEquals(DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
chain.maybeLookAhead();
assertEquals(key1.getPubKeyPoint(), chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
assertEquals(key2.getPubKeyPoint(), chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
final DeterministicKey key = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key3.getPubKeyPoint(), key.getPubKeyPoint());
try {
// Can't sign with a key from a watching chain.
key.sign(Sha256Hash.ZERO_HASH);
fail();
} catch (ECKey.MissingPrivateKeyException e) {
// Ignored.
}
// Test we can serialize and deserialize a watching chain OK.
List<Protos.Key> serialization = chain.serializeToProtobuf();
checkSerialization(serialization, "watching-wallet-serialization.txt");
chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
final DeterministicKey rekey4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
@Test(expected = IllegalStateException.class)
public void watchingCannotEncrypt() throws Exception {
final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH);
chain = DeterministicKeyChain.watch(accountKey.getPubOnly());
chain = chain.toEncrypted("this doesn't make any sense");
}
@Test
public void bloom1() {
DeterministicKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
int numEntries =
(((chain.getLookaheadSize() + chain.getLookaheadThreshold()) * 2) // * 2 because of internal/external
+ chain.numLeafKeysIssued()
+ 4 // one root key + one account key + two chain keys (internal/external)
) * 2; // because the filter contains keys and key hashes.
assertEquals(numEntries, chain.numBloomFilterEntries());
BloomFilter filter = chain.getFilter(numEntries, 0.001, 1);
assertTrue(filter.contains(key1.getPubKey()));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key2.getPubKey()));
assertTrue(filter.contains(key2.getPubKeyHash()));
// The lookahead zone is tested in bloom2 and via KeyChainGroupTest.bloom
}
@Test
public void bloom2() throws Exception {
// Verify that if when we watch a key, the filter contains at least 100 keys.
DeterministicKey[] keys = new DeterministicKey[100];
for (int i = 0; i < keys.length; i++)
keys[i] = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain = DeterministicKeyChain.watch(chain.getWatchingKey());
int e = chain.numBloomFilterEntries();
BloomFilter filter = chain.getFilter(e, 0.001, 1);
for (DeterministicKey key : keys)
assertTrue("key " + key, filter.contains(key.getPubKeyHash()));
}
private String protoToString(List<Protos.Key> keys) {
StringBuilder sb = new StringBuilder();
for (Protos.Key key : keys) {
sb.append(key.toString());
sb.append("\n");
}
return sb.toString().trim();
}
private String checkSerialization(List<Protos.Key> keys, String filename) {
try {
String sb = protoToString(keys);
String expected = Utils.getResourceAsString(getClass().getResource(filename));
assertEquals(expected, sb);
return expected;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,588 +0,0 @@
/**
* Copyright 2014 Mike Hearn
*
* 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.wallet;
import com.dogecoin.dogecoinj.core.*;
import com.dogecoin.dogecoinj.crypto.DeterministicKey;
import com.dogecoin.dogecoinj.crypto.KeyCrypterException;
import com.dogecoin.dogecoinj.crypto.KeyCrypterScrypt;
import com.dogecoin.dogecoinj.crypto.MnemonicCode;
import com.dogecoin.dogecoinj.params.MainNetParams;
import com.dogecoin.dogecoinj.utils.BriefLogFormatter;
import com.dogecoin.dogecoinj.utils.Threading;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
import static org.junit.Assert.assertArrayEquals;
public class KeyChainGroupTest {
// Number of initial keys in this tests HD wallet, including interior keys.
private static final int INITIAL_KEYS = 4;
private static final int LOOKAHEAD_SIZE = 5;
private static final NetworkParameters params = MainNetParams.get();
private static final String XPUB = "xpub68KFnj3bqUx1s7mHejLDBPywCAKdJEu1b49uniEEn2WSbHmZ7xbLqFTjJbtx1LUcAt1DwhoqWHmo2s5WMJp6wi38CiF2hYD49qVViKVvAoi";
private KeyChainGroup group;
private DeterministicKey watchingAccountKey;
@Before
public void setup() {
BriefLogFormatter.init();
Utils.setMockClock();
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
group.getActiveKeyChain(); // Force create a chain.
watchingAccountKey = DeterministicKey.deserializeB58(null, XPUB, params);
}
private KeyChainGroup createMarriedKeyChainGroup() {
KeyChainGroup group = new KeyChainGroup(params);
DeterministicKeyChain chain = createMarriedKeyChain();
group.addAndActivateHDChain(chain);
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.getActiveKeyChain();
return group;
}
private MarriedKeyChain createMarriedKeyChain() {
byte[] entropy = Sha256Hash.create("don't use a seed like this in real life".getBytes()).getBytes();
DeterministicSeed seed = new DeterministicSeed(entropy, "", MnemonicCode.BIP39_STANDARDISATION_TIME_SECS);
MarriedKeyChain chain = MarriedKeyChain.builder()
.seed(seed)
.followingKeys(watchingAccountKey)
.threshold(2).build();
return chain;
}
@Test
public void freshCurrentKeys() throws Exception {
int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
+ 1 // keys issued
+ 3 /* account key + int/ext parent keys */;
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey r1 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey i1 = new ECKey();
group.importKeys(i1);
numKeys++;
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey r2 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(r1, r2);
ECKey c1 = group.currentKey(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(r1, c1);
ECKey r3 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(r1, r3);
ECKey c2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(r3, c2);
// Current key has not moved and will not under marked as used.
ECKey r4 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(r2, r4);
ECKey c3 = group.currentKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(c1, c3);
// Mark as used. Current key is now different.
group.markPubKeyAsUsed(r4.getPubKey());
ECKey r5 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(r4, r5);
}
@Test
public void freshCurrentKeysForMarriedKeychain() throws Exception {
group = createMarriedKeyChainGroup();
try {
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
fail();
} catch (UnsupportedOperationException e) {
}
try {
group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
fail();
} catch (UnsupportedOperationException e) {
}
}
@Test
public void imports() throws Exception {
ECKey key1 = new ECKey();
int numKeys = group.numKeys();
assertFalse(group.removeImportedKey(key1));
assertEquals(1, group.importKeys(ImmutableList.of(key1)));
assertEquals(numKeys + 1, group.numKeys()); // Lookahead is triggered by requesting a key, so none yet.
group.removeImportedKey(key1);
assertEquals(numKeys, group.numKeys());
}
@Test
public void findKey() throws Exception {
ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
ECKey b = group.freshKey(KeyChain.KeyPurpose.CHANGE);
ECKey c = new ECKey();
ECKey d = new ECKey(); // Not imported.
group.importKeys(c);
assertTrue(group.hasKey(a));
assertTrue(group.hasKey(b));
assertTrue(group.hasKey(c));
assertFalse(group.hasKey(d));
ECKey result = group.findKeyFromPubKey(a.getPubKey());
assertEquals(a, result);
result = group.findKeyFromPubKey(b.getPubKey());
assertEquals(b, result);
result = group.findKeyFromPubHash(a.getPubKeyHash());
assertEquals(a, result);
result = group.findKeyFromPubHash(b.getPubKeyHash());
assertEquals(b, result);
result = group.findKeyFromPubKey(c.getPubKey());
assertEquals(c, result);
result = group.findKeyFromPubHash(c.getPubKeyHash());
assertEquals(c, result);
assertNull(group.findKeyFromPubKey(d.getPubKey()));
assertNull(group.findKeyFromPubHash(d.getPubKeyHash()));
}
@Test
public void currentP2SHAddress() throws Exception {
group = createMarriedKeyChainGroup();
Address a1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(a1.isP2SHAddress());
Address a2 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(a1, a2);
Address a3 = group.currentAddress(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(a2, a3);
}
@Test
public void freshAddress() throws Exception {
group = createMarriedKeyChainGroup();
Address a1 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Address a2 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(a1.isP2SHAddress());
assertNotEquals(a1, a2);
group.getBloomFilterElementCount();
assertEquals(((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
+ (2 - group.getLookaheadThreshold()) // keys issued
+ 4 /* master, account, int, ext */, group.numKeys());
Address a3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(a2, a3);
}
@Test
public void findRedeemData() throws Exception {
group = createMarriedKeyChainGroup();
// test script hash that we don't have
assertNull(group.findRedeemDataFromScriptHash(new ECKey().getPubKey()));
// test our script hash
Address address = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
RedeemData redeemData = group.findRedeemDataFromScriptHash(address.getHash160());
assertNotNull(redeemData);
assertNotNull(redeemData.redeemScript);
assertEquals(2, redeemData.keys.size());
}
// Check encryption with and without a basic keychain.
@Test
public void encryptionWithoutImported() throws Exception {
encryption(false);
}
@Test
public void encryptionWithImported() throws Exception {
encryption(true);
}
public void encryption(boolean withImported) throws Exception {
Utils.rollMockClock(0);
long now = Utils.currentTimeSeconds();
ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(now, group.getEarliestKeyCreationTime());
Utils.rollMockClock(-86400);
long yesterday = Utils.currentTimeSeconds();
ECKey b = new ECKey();
assertFalse(group.isEncrypted());
try {
group.checkPassword("foo"); // Cannot check password of an unencrypted group.
fail();
} catch (IllegalStateException e) {
}
if (withImported) {
assertEquals(now, group.getEarliestKeyCreationTime());
group.importKeys(b);
assertEquals(yesterday, group.getEarliestKeyCreationTime());
}
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
assertTrue(group.isEncrypted());
assertTrue(group.checkPassword("password"));
assertFalse(group.checkPassword("wrong password"));
final ECKey ea = group.findKeyFromPubKey(a.getPubKey());
assertTrue(checkNotNull(ea).isEncrypted());
if (withImported) {
assertTrue(checkNotNull(group.findKeyFromPubKey(b.getPubKey())).isEncrypted());
assertEquals(yesterday, group.getEarliestKeyCreationTime());
} else {
assertEquals(now, group.getEarliestKeyCreationTime());
}
try {
ea.sign(Sha256Hash.ZERO_HASH);
fail();
} catch (ECKey.KeyIsEncryptedException e) {
// Ignored.
}
if (withImported) {
ECKey c = new ECKey();
try {
group.importKeys(c);
fail();
} catch (KeyCrypterException e) {
}
group.importKeysAndEncrypt(ImmutableList.of(c), aesKey);
ECKey ec = group.findKeyFromPubKey(c.getPubKey());
try {
group.importKeysAndEncrypt(ImmutableList.of(ec), aesKey);
fail();
} catch (IllegalArgumentException e) {
}
}
try {
group.decrypt(scrypt.deriveKey("WRONG PASSWORD"));
fail();
} catch (KeyCrypterException e) {
}
group.decrypt(aesKey);
assertFalse(group.isEncrypted());
assertFalse(checkNotNull(group.findKeyFromPubKey(a.getPubKey())).isEncrypted());
if (withImported) {
assertFalse(checkNotNull(group.findKeyFromPubKey(b.getPubKey())).isEncrypted());
assertEquals(yesterday, group.getEarliestKeyCreationTime());
} else {
assertEquals(now, group.getEarliestKeyCreationTime());
}
}
@Test
public void encryptionWhilstEmpty() throws Exception {
group = new KeyChainGroup(params);
group.setLookaheadSize(5);
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
assertTrue(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).isEncrypted());
final ECKey key = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.decrypt(aesKey);
assertFalse(checkNotNull(group.findKeyFromPubKey(key.getPubKey())).isEncrypted());
}
@Test
public void bloom() throws Exception {
ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
ECKey key2 = new ECKey();
BloomFilter filter = group.getBloomFilter(group.getBloomFilterElementCount(), 0.001, (long)(Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key1.getPubKey()));
assertFalse(filter.contains(key2.getPubKey()));
// Check that the filter contains the lookahead buffer and threshold zone.
for (int i = 0; i < LOOKAHEAD_SIZE + group.getLookaheadThreshold(); i++) {
ECKey k = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(filter.contains(k.getPubKeyHash()));
}
// We ran ahead of the lookahead buffer.
assertFalse(filter.contains(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKey()));
group.importKeys(key2);
filter = group.getBloomFilter(group.getBloomFilterElementCount(), 0.001, (long) (Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key1.getPubKey()));
assertTrue(filter.contains(key2.getPubKey()));
}
@Test
public void findRedeemScriptFromPubHash() throws Exception {
group = createMarriedKeyChainGroup();
Address address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group.findRedeemDataFromScriptHash(address.getHash160()) != null);
group.getBloomFilterElementCount();
KeyChainGroup group2 = createMarriedKeyChainGroup();
group2.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group2.getBloomFilterElementCount(); // Force lookahead.
// test address from lookahead zone and lookahead threshold zone
for (int i = 0; i < group.getLookaheadSize() + group.getLookaheadThreshold(); i++) {
address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group2.findRedeemDataFromScriptHash(address.getHash160()) != null);
}
assertFalse(group2.findRedeemDataFromScriptHash(group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS).getHash160()) != null);
}
@Test
public void bloomFilterForMarriedChains() throws Exception {
group = createMarriedKeyChainGroup();
int bufferSize = group.getLookaheadSize() + group.getLookaheadThreshold();
int expected = bufferSize * 2 /* chains */ * 2 /* elements */;
assertEquals(expected, group.getBloomFilterElementCount());
Address address1 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(expected, group.getBloomFilterElementCount());
BloomFilter filter = group.getBloomFilter(expected + 2, 0.001, (long)(Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(address1.getHash160()));
Address address2 = group.freshAddress(KeyChain.KeyPurpose.CHANGE);
assertTrue(filter.contains(address2.getHash160()));
// Check that the filter contains the lookahead buffer.
for (int i = 0; i < bufferSize - 1 /* issued address */; i++) {
Address address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue("key " + i, filter.contains(address.getHash160()));
}
// We ran ahead of the lookahead buffer.
assertFalse(filter.contains(group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS).getHash160()));
}
@Test
public void earliestKeyTime() throws Exception {
long now = Utils.currentTimeSeconds(); // mock
long yesterday = now - 86400;
assertEquals(now, group.getEarliestKeyCreationTime());
Utils.rollMockClock(10000);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Utils.rollMockClock(10000);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
// Check that all keys are assumed to be created at the same instant the seed is.
assertEquals(now, group.getEarliestKeyCreationTime());
ECKey key = new ECKey();
key.setCreationTimeSeconds(yesterday);
group.importKeys(key);
assertEquals(yesterday, group.getEarliestKeyCreationTime());
}
@Test
public void events() throws Exception {
// Check that events are registered with the right chains and that if a chain is added, it gets the event
// listeners attached properly even post-hoc.
final AtomicReference<ECKey> ran = new AtomicReference<ECKey>(null);
final KeyChainEventListener listener = new KeyChainEventListener() {
@Override
public void onKeysAdded(List<ECKey> keys) {
ran.set(keys.get(0));
}
};
group.addEventListener(listener, Threading.SAME_THREAD);
ECKey key = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(key, ran.getAndSet(null));
ECKey key2 = new ECKey();
group.importKeys(key2);
assertEquals(key2, ran.getAndSet(null));
group.removeEventListener(listener);
ECKey key3 = new ECKey();
group.importKeys(key3);
assertNull(ran.get());
}
@Test
public void serialization() throws Exception {
assertEquals(INITIAL_KEYS + 1 /* for the seed */, group.serializeToProtobuf().size());
group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf());
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
group.getBloomFilterElementCount();
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
group.importKeys(new ECKey());
List<Protos.Key> protoKeys2 = group.serializeToProtobuf();
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
assertEquals(key2, group.currentKey(KeyChain.KeyPurpose.CHANGE));
assertEquals(key1, group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS));
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys2);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
List<Protos.Key> protoKeys3 = group.serializeToProtobuf();
group = KeyChainGroup.fromProtobufEncrypted(params, protoKeys3, scrypt);
assertTrue(group.isEncrypted());
assertTrue(group.checkPassword("password"));
group.decrypt(aesKey);
// No need for extensive contents testing here, as that's done in the keychain class tests.
}
@Test
public void serializeWatching() throws Exception {
group = new KeyChainGroup(params, watchingAccountKey);
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.freshKey(KeyChain.KeyPurpose.CHANGE);
group.getBloomFilterElementCount(); // Force lookahead.
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, protoKeys1.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1);
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, group.serializeToProtobuf().size());
}
@Test
public void serializeMarried() throws Exception {
group = createMarriedKeyChainGroup();
Address address1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group.isMarried());
assertEquals(2, group.getActiveKeyChain().getSigsRequiredToSpend());
List<Protos.Key> protoKeys = group.serializeToProtobuf();
KeyChainGroup group2 = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys);
assertTrue(group2.isMarried());
assertEquals(2, group.getActiveKeyChain().getSigsRequiredToSpend());
Address address2 = group2.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(address1, address2);
}
@Test
public void addFollowingAccounts() throws Exception {
assertFalse(group.isMarried());
group.addAndActivateHDChain(createMarriedKeyChain());
assertTrue(group.isMarried());
}
@Test
public void constructFromSeed() throws Exception {
ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
final DeterministicSeed seed = checkNotNull(group.getActiveKeyChain().getSeed());
KeyChainGroup group2 = new KeyChainGroup(params, seed);
group2.setLookaheadSize(5);
ECKey key2 = group2.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(key1, key2);
}
@Test(expected = DeterministicUpgradeRequiredException.class)
public void deterministicUpgradeRequired() throws Exception {
// Check that if we try to use HD features in a KCG that only has random keys, we get an exception.
group = new KeyChainGroup(params);
group.importKeys(new ECKey(), new ECKey());
assertTrue(group.isDeterministicUpgradeRequired());
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); // throws
}
@Test
public void deterministicUpgradeUnencrypted() throws Exception {
// Check that a group that contains only random keys has its HD chain created using the private key bytes of
// the oldest random key, so upgrading the same wallet twice gives the same outcome.
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
ECKey key2 = new ECKey();
group.importKeys(key2, key1);
List<Protos.Key> protobufs = group.serializeToProtobuf();
group.upgradeToDeterministic(0, null);
assertFalse(group.isDeterministicUpgradeRequired());
DeterministicKey dkey1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicSeed seed1 = group.getActiveKeyChain().getSeed();
assertNotNull(seed1);
group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs);
group.upgradeToDeterministic(0, null); // Should give same result as last time.
DeterministicKey dkey2 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicSeed seed2 = group.getActiveKeyChain().getSeed();
assertEquals(seed1, seed2);
assertEquals(dkey1, dkey2);
// Check we used the right (oldest) key despite backwards import order.
byte[] truncatedBytes = Arrays.copyOfRange(key1.getSecretBytes(), 0, 16);
assertArrayEquals(seed1.getEntropyBytes(), truncatedBytes);
}
@Test
public void deterministicUpgradeRotating() throws Exception {
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
long now = Utils.currentTimeSeconds();
ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
ECKey key2 = new ECKey();
Utils.rollMockClock(86400);
ECKey key3 = new ECKey();
group.importKeys(key2, key1, key3);
group.upgradeToDeterministic(now + 10, null);
DeterministicSeed seed = group.getActiveKeyChain().getSeed();
assertNotNull(seed);
// Check we used the right key: oldest non rotating.
byte[] truncatedBytes = Arrays.copyOfRange(key2.getSecretBytes(), 0, 16);
assertArrayEquals(seed.getEntropyBytes(), truncatedBytes);
}
@Test
public void deterministicUpgradeEncrypted() throws Exception {
group = new KeyChainGroup(params);
final ECKey key = new ECKey();
group.importKeys(key);
final KeyCrypterScrypt crypter = new KeyCrypterScrypt();
final KeyParameter aesKey = crypter.deriveKey("abc");
assertTrue(group.isDeterministicUpgradeRequired());
group.encrypt(crypter, aesKey);
assertTrue(group.isDeterministicUpgradeRequired());
try {
group.upgradeToDeterministic(0, null);
fail();
} catch (DeterministicUpgradeRequiresPassword e) {
// Expected.
}
group.upgradeToDeterministic(0, aesKey);
assertFalse(group.isDeterministicUpgradeRequired());
final DeterministicSeed deterministicSeed = group.getActiveKeyChain().getSeed();
assertNotNull(deterministicSeed);
assertTrue(deterministicSeed.isEncrypted());
byte[] entropy = checkNotNull(group.getActiveKeyChain().toDecrypted(aesKey).getSeed()).getEntropyBytes();
// Check we used the right key: oldest non rotating.
byte[] truncatedBytes = Arrays.copyOfRange(key.getSecretBytes(), 0, 16);
assertArrayEquals(entropy, truncatedBytes);
}
@Test
public void markAsUsed() throws Exception {
Address addr1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Address addr2 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(addr1, addr2);
group.markPubKeyHashAsUsed(addr1.getHash160());
Address addr3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(addr2, addr3);
}
}

View File

@ -0,0 +1,50 @@
package org.bitcoinj.core;
import java.io.ByteArrayOutputStream;
import org.bitcoinj.params.TestNet3Params;
import org.junit.Before;
import org.junit.Test;
import static org.bitcoinj.core.CoinbaseBlockTest.getBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* AuxPoW header parsing/serialization and validation
*/
public class AuxPoWTest {
static final NetworkParameters params = TestNet3Params.get();
/**
* Parse the AuxPoW header from Dogecoin block #403,931.
*/
@Test
public void parseAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false);
MerkleBranch branch = auxpow.getCoinbaseBranch();
Sha256Hash expected = new Sha256Hash("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf");
assertEquals(expected, auxpow.getCoinbase().getHash());
assertEquals(3, auxpow.getCoinbaseBranch().getSize());
assertEquals(6, auxpow.getBlockchainBranch().getSize());
expected = new Sha256Hash("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c");
assertEquals(expected, auxpow.getParentBlockHeader().getHash());
}
/**
* Test serializing the AuxPoW header from Dogecoin block #403,931.
*/
@Test
public void serializeAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false);
byte[] expected = auxpowAsBytes;
byte[] actual = auxpow.bitcoinSerialize();
assertArrayEquals(expected, actual);
}
}

View File

@ -0,0 +1,62 @@
package org.bitcoinj.core;
import java.io.ByteArrayOutputStream;
import org.bitcoinj.params.TestNet3Params;
import org.junit.Before;
import org.junit.Test;
import static org.bitcoinj.core.CoinbaseBlockTest.getBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Check merkle branch parsing and root calculation.
*/
public class MerkleBranchTest {
static final NetworkParameters params = TestNet3Params.get();
/**
* Parse the coinbase merkle branch from Dogecoin block #403,931.
*/
@Test
public void parseMerkleBranch() throws Exception {
byte[] branchAsBytes = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, branchAsBytes, 0);
Sha256Hash[] expected = new Sha256Hash[] {
new Sha256Hash("be079078869399faccaa764c10e9df6e9981701759ad18e13724d9ca58831348"),
new Sha256Hash("5f5bfb2c79541778499cab956a103887147f2ab5d4a717f32f9eeebd29e1f894"),
new Sha256Hash("d8c6fe42ca25076159cd121a5e20c48c1bc53ab90730083e44a334566ea6bbcb")
};
assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.getSize()]));
}
/**
* Parse the transaction merkle branch from Dogecoin block #403,931, then
* serialize it back again to verify serialization works.
*/
@Test
public void serializeMerkleBranch() throws Exception {
byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0,
false, false);
byte[] actual = branch.bitcoinSerialize();
assertArrayEquals(expected, actual);
}
/**
* Calculate the AuxPoW merkle branch root from Dogecoin block #403,931.
*/
@Test
public void calculateRootBranch() throws Exception {
byte[] branchAsBytes = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch2.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, branchAsBytes, 0);
Sha256Hash txId = new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609");
Sha256Hash expected = new Sha256Hash("ce3040fdb7e37484f6a1ca4f8f5da81e6b7e404ec91102315a233e03a0c39c95");
assertEquals(expected, branch.calculateMerkleRoot(txId));
}
}