Skip to content
Browse files

merged upstream

  • Loading branch information...
2 parents 1fd48bc + 0683c06 commit af7a570a8f59a03c6901765fc7726785d9f1fe44 Brian Armstrong committed Jul 9, 2011
Showing with 451 additions and 274 deletions.
  1. +2 −1 AUTHORS
  2. +1 −1 pom.xml
  3. +26 −16 src/com/google/bitcoin/core/BitcoinSerializer.java
  4. +45 −44 src/com/google/bitcoin/core/Block.java
  5. +63 −13 src/com/google/bitcoin/core/BlockChain.java
  6. +8 −8 src/com/google/bitcoin/core/GetBlocksMessage.java
  7. +3 −3 src/com/google/bitcoin/core/InventoryItem.java
  8. +2 −2 src/com/google/bitcoin/core/ListMessage.java
  9. +0 −87 src/com/google/bitcoin/core/MemoryBlockStore.java
  10. +2 −2 src/com/google/bitcoin/core/Message.java
  11. +2 −2 src/com/google/bitcoin/core/NetworkParameters.java
  12. +9 −9 src/com/google/bitcoin/core/Peer.java
  13. +2 −5 src/com/google/bitcoin/core/Ping.java
  14. +35 −11 src/com/google/bitcoin/core/Sha256Hash.java
  15. +3 −0 src/com/google/bitcoin/core/StoredBlock.java
  16. +5 −1 src/com/google/bitcoin/core/Transaction.java
  17. +3 −7 src/com/google/bitcoin/core/TransactionInput.java
  18. +4 −5 src/com/google/bitcoin/core/TransactionOutPoint.java
  19. +1 −1 src/com/google/bitcoin/core/VersionMessage.java
  20. +12 −1 src/com/google/bitcoin/core/Wallet.java
  21. +8 −3 src/com/google/bitcoin/{core → discovery}/DnsDiscovery.java
  22. +4 −1 src/com/google/bitcoin/{core → discovery}/IrcDiscovery.java
  23. +1 −1 src/com/google/bitcoin/{core → discovery}/PeerDiscovery.java
  24. +1 −1 src/com/google/bitcoin/{core → discovery}/PeerDiscoveryException.java
  25. +6 −2 src/com/google/bitcoin/{core → discovery}/SeedPeers.java
  26. +46 −0 src/com/google/bitcoin/examples/FetchBlock.java
  27. +2 −0 src/com/google/bitcoin/examples/PingService.java
  28. +3 −0 src/com/google/bitcoin/examples/PrintPeers.java
  29. +1 −0 src/com/google/bitcoin/examples/PrivateKeys.java
  30. +1 −0 src/com/google/bitcoin/examples/RefreshWallet.java
  31. +5 −2 src/com/google/bitcoin/{core → store}/BlockStore.java
  32. +1 −1 src/com/google/bitcoin/{core → store}/BlockStoreException.java
  33. +11 −12 src/com/google/bitcoin/{core → store}/BoundedOverheadBlockStore.java
  34. +12 −12 src/com/google/bitcoin/{core → store}/DiskBlockStore.java
  35. +64 −0 src/com/google/bitcoin/store/MemoryBlockStore.java
  36. +6 −5 tests/com/google/bitcoin/core/BlockChainTest.java
  37. +1 −0 tests/com/google/bitcoin/core/ChainSplitTests.java
  38. +29 −8 tests/com/google/bitcoin/core/WalletTest.java
  39. +5 −3 tests/com/google/bitcoin/{core → discovery}/IrcDiscoveryTest.java
  40. +4 −1 tests/com/google/bitcoin/{core → discovery}/SeedPeersTest.java
  41. +6 −1 tests/com/google/bitcoin/{core → store}/BoundedOverheadBlockStoreTest.java
  42. +6 −2 tests/com/google/bitcoin/{core → store}/DiskBlockStoreTest.java
View
3 AUTHORS
@@ -5,4 +5,5 @@ Thilo Planz <thilo@cpan.org>
Micheal Swiggs <bobby.simpson87@gmail.com>
Gary Rowe <g.rowe@froot.co.uk>
Noa Resare <noa@resare.com>
-John Sample <jwsample@gmail.com>
+John Sample <jwsample@gmail.com>
+Jan Møller <jan.moller@gmail.com>
View
2 pom.xml
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google</groupId>
<artifactId>bitcoinj</artifactId>
- <version>0.2</version>
+ <version>0.3-SNAPSHOT</version>
<name>BitCoinJ</name>
<description>A Java implementation of a Bitcoin client-only node</description>
<url>http://code.google.com/p/bitcoinj</url>
View
42 src/com/google/bitcoin/core/BitcoinSerializer.java
@@ -51,8 +51,6 @@
private boolean usesChecksumming;
private static Map<Class<? extends Message>, String> names = new HashMap<Class<? extends Message>,String>();
- private static Map<String, Constructor<? extends Message>>
- messageConstructors = new HashMap<String, Constructor<? extends Message>>();
static {
names.put(VersionMessage.class, "version");
@@ -75,14 +73,6 @@
public BitcoinSerializer(NetworkParameters params, boolean usesChecksumming) {
this.params = params;
this.usesChecksumming = usesChecksumming;
-
- // some Message subclasses can only be sent for now, ignore missing constructors
- for (Class<? extends Message> c : names.keySet()) {
- Constructor<? extends Message> ct = makeConstructor(c);
- if (ct != null) {
- messageConstructors.put(names.get(c),ct);
- }
- }
}
public void useChecksumming(boolean usesChecksumming) {
@@ -121,7 +111,8 @@ public void serialize(Message message, OutputStream out) throws IOException {
out.write(header);
out.write(payload);
- log.debug("Sending {} message: {}", name, bytesToHexString(header) + bytesToHexString(payload));
+ if (log.isDebugEnabled())
+ log.debug("Sending {} message: {}", name, bytesToHexString(header) + bytesToHexString(payload));
}
/**
@@ -216,17 +207,36 @@ public Message deserialize(InputStream in) throws ProtocolException, IOException
}
try {
- Constructor<? extends Message> c = messageConstructors.get(command);
- if (c == null) {
- throw new ProtocolException("No support for deserializing message with name " + command);
- }
- return c.newInstance(params, payloadBytes);
+ return makeMessage(command, payloadBytes);
} catch (Exception e) {
throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e);
}
}
+ private Message makeMessage(String command, byte[] payloadBytes) throws ProtocolException {
+ // We use an if ladder rather than reflection because reflection is very slow on Android.
+ if (command.equals("version")) {
+ return new VersionMessage(params, payloadBytes);
+ } else if (command.equals("inv")) {
+ return new InventoryMessage(params, payloadBytes);
+ } else if (command.equals("block")) {
+ return new Block(params, payloadBytes);
+ } else if (command.equals("getdata")) {
+ return new GetDataMessage(params, payloadBytes);
+ } else if (command.equals("tx")) {
+ return new Transaction(params, payloadBytes);
+ } else if (command.equals("addr")) {
+ return new AddressMessage(params, payloadBytes);
+ } else if (command.equals("ping")) {
+ return new Ping();
+ } else if (command.equals("verack")) {
+ return new VersionAck(params, payloadBytes);
+ } else {
+ throw new ProtocolException("No support for deserializing message with name " + command);
+ }
+ }
+
private Constructor<? extends Message> makeConstructor(Class<? extends Message> c) {
Class<?> parTypes[] = new Class<?>[2];
parTypes[0] = NetworkParameters.class;
View
89 src/com/google/bitcoin/core/Block.java
@@ -33,7 +33,7 @@
* over its contents. See the BitCoin technical paper 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(byte[])}, or grab one from a downloaded {@link BlockChain}.
+ * or request one specifically using {@link Peer#getBlock(Sha256Hash)}, or grab one from a downloaded {@link BlockChain}.
*/
public class Block extends Message {
private static final Logger log = LoggerFactory.getLogger(Block.class);
@@ -49,27 +49,28 @@
// For unit testing. If not zero, use this instead of the current time.
static long fakeClock = 0;
+
+ // Fields defined as part of the protocol format.
private long version;
- private byte[] prevBlockHash;
- private byte[] merkleRoot;
+ private Sha256Hash prevBlockHash;
+ private Sha256Hash merkleRoot;
private long time;
private long difficultyTarget; // "nBits"
-
private long nonce;
/** If null, it means this object holds only the headers. */
List<Transaction> transactions;
/** Stores the hash of the block. If null, getHash() will recalculate it. */
- private transient byte[] hash;
+ private transient Sha256Hash hash;
- /** Special case constructor, used for the genesis node and unit tests. */
+ /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
Block(NetworkParameters params) {
super(params);
// Set up a few basic things. We are not complete after this though.
version = 1;
difficultyTarget = 0x1d07fff8L;
time = System.currentTimeMillis() / 1000;
- prevBlockHash = new byte[32]; // All zeros.
+ prevBlockHash = Sha256Hash.ZERO_HASH;
}
/** Constructs a block object from the BitCoin wire format. */
@@ -85,7 +86,7 @@ void parse() throws ProtocolException {
difficultyTarget = readUint32();
nonce = readUint32();
- hash = Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor));
+ hash = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor)));
if (cursor == bytes.length) {
// This message is just a header, it has no transactions.
@@ -103,8 +104,8 @@ void parse() throws ProtocolException {
private void writeHeader(OutputStream stream) throws IOException {
Utils.uint32ToByteStreamLE(version, stream);
- stream.write(Utils.reverseBytes(prevBlockHash));
- stream.write(Utils.reverseBytes(getMerkleRoot()));
+ stream.write(Utils.reverseBytes(prevBlockHash.getBytes()));
+ stream.write(Utils.reverseBytes(getMerkleRoot().getBytes()));
Utils.uint32ToByteStreamLE(time, stream);
Utils.uint32ToByteStreamLE(difficultyTarget, stream);
Utils.uint32ToByteStreamLE(nonce, stream);
@@ -124,11 +125,11 @@ void bitcoinSerializeToStream(OutputStream stream) throws IOException {
/**
* Calculates the block hash by serializing the block and hashing the resulting bytes.
*/
- private byte[] calculateHash() {
+ private Sha256Hash calculateHash() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
writeHeader(bos);
- return Utils.reverseBytes(doubleDigest(bos.toByteArray()));
+ return new Sha256Hash(Utils.reverseBytes(doubleDigest(bos.toByteArray())));
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
@@ -140,13 +141,13 @@ void bitcoinSerializeToStream(OutputStream stream) throws IOException {
* "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048".
*/
public String getHashAsString() {
- return Utils.bytesToHexString(getHash());
+ return getHash().toString();
}
/**
* Returns the hash of the block (which for a valid, solved block should be below the target). Big endian.
*/
- public byte[] getHash() {
+ public Sha256Hash getHash() {
if (hash == null)
hash = calculateHash();
return hash;
@@ -169,14 +170,16 @@ public BigInteger getWork() throws VerificationException {
/** Returns a copy of the block, but without any transactions. */
public Block cloneAsHeader() {
- try {
- Block block = new Block(params, bitcoinSerialize());
- block.transactions = null;
- return block;
- } catch (ProtocolException e) {
- // Should not be able to happen unless our state is internally inconsistent.
- throw new RuntimeException(e);
- }
+ Block block = new Block(params);
+ block.nonce = nonce;
+ block.prevBlockHash = prevBlockHash.clone();
+ block.merkleRoot = getMerkleRoot().clone();
+ block.version = version;
+ block.time = time;
+ block.difficultyTarget = difficultyTarget;
+ block.transactions = null;
+ block.hash = getHash().clone();
+ return block;
}
/**
@@ -186,8 +189,8 @@ public Block cloneAsHeader() {
@Override
public String toString() {
StringBuffer s = new StringBuffer("v" + version + " block: \n" +
- " previous block: " + bytesToHexString(prevBlockHash) + "\n" +
- " merkle root: " + bytesToHexString(getMerkleRoot()) + "\n" +
+ " previous block: " + prevBlockHash.toString() + "\n" +
+ " merkle root: " + getMerkleRoot().toString() + "\n" +
" time: [" + time + "] " + new Date(time * 1000).toString() + "\n" +
" difficulty target (nBits): " + difficultyTarget + "\n" +
" nonce: " + nonce + "\n");
@@ -244,7 +247,7 @@ private boolean checkProofOfWork(boolean throwException) throws VerificationExce
// field is of the right value. This requires us to have the preceeding blocks.
BigInteger target = getDifficultyTargetAsInteger();
- BigInteger h = new BigInteger(1, getHash());
+ BigInteger h = getHash().toBigInteger();
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
@@ -263,21 +266,18 @@ private void checkTimestamp() throws VerificationException {
throw new VerificationException("Block too far in future");
}
- private void checkMerkleHash() throws VerificationException {
- List<byte[]> tree = buildMerkleTree();
- byte[] calculatedRoot = tree.get(tree.size() - 1);
- if (!Arrays.equals(calculatedRoot, merkleRoot)) {
- log.error("Merkle tree did not verify: ");
- for (byte[] b : tree) log.error(Utils.bytesToHexString(b));
-
+ private void checkMerkleRoot() throws VerificationException {
+ Sha256Hash calculatedRoot = calculateMerkleRoot();
+ if (!calculatedRoot.equals(merkleRoot)) {
+ log.error("Merkle tree did not verify");
throw new VerificationException("Merkle hashes do not match: " +
- bytesToHexString(calculatedRoot) + " vs " + bytesToHexString(merkleRoot));
+ calculatedRoot + " vs " + merkleRoot);
}
}
- private byte[] calculateMerkleRoot() {
+ private Sha256Hash calculateMerkleRoot() {
List<byte[]> tree = buildMerkleTree();
- return tree.get(tree.size() - 1);
+ return new Sha256Hash(tree.get(tree.size() - 1));
}
private List<byte[]> buildMerkleTree() {
@@ -315,7 +315,7 @@ private void checkMerkleHash() throws VerificationException {
ArrayList<byte[]> tree = new ArrayList<byte[]>();
// Start by adding all the hashes of the transactions as leaves of the tree.
for (Transaction t : transactions) {
- tree.add(t.getHash().hash);
+ tree.add(t.getHash().getBytes());
}
int levelOffset = 0; // Offset in the list where the currently processed level starts.
// Step through each level, stopping when we reach the root (levelSize == 1).
@@ -369,31 +369,31 @@ public void verify() throws VerificationException {
if (transactions != null) {
assert transactions.size() > 0;
checkTransactions();
- checkMerkleHash();
+ checkMerkleRoot();
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Block)) return false;
Block other = (Block) o;
- return Arrays.equals(getHash(), other.getHash());
+ return getHash().equals(other.getHash());
}
@Override
public int hashCode() {
- return Arrays.hashCode(getHash());
+ return getHash().hashCode();
}
/** Returns the merkle root in big endian form, calculating it from transactions if necessary. */
- public byte[] getMerkleRoot() {
+ public Sha256Hash getMerkleRoot() {
if (merkleRoot == null)
merkleRoot = calculateMerkleRoot();
return merkleRoot;
}
/** Exists only for unit testing. */
- void setMerkleRoot(byte[] value) {
+ void setMerkleRoot(Sha256Hash value) {
merkleRoot = value;
hash = null;
}
@@ -415,11 +415,11 @@ public long getVersion() {
}
/** Returns the hash of the previous block in the chain, as defined by the block header. */
- public byte[] getPrevBlockHash() {
+ public Sha256Hash getPrevBlockHash() {
return prevBlockHash;
}
- void setPrevBlockHash(byte[] prevBlockHash) {
+ void setPrevBlockHash(Sha256Hash prevBlockHash) {
this.prevBlockHash = prevBlockHash;
this.hash = null;
}
@@ -497,7 +497,8 @@ Block createNextBlock(Address to, long time) {
return b;
}
- Block createNextBlock(Address to) {
+ // Visible for testing.
+ public Block createNextBlock(Address to) {
return createNextBlock(to, System.currentTimeMillis() / 1000);
}
}
View
76 src/com/google/bitcoin/core/BlockChain.java
@@ -19,6 +19,8 @@
import java.math.BigInteger;
import java.util.*;
+import com.google.bitcoin.store.BlockStore;
+import com.google.bitcoin.store.BlockStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,7 +65,7 @@
protected StoredBlock chainHead;
protected final NetworkParameters params;
- protected final Wallet wallet;
+ protected final List<Wallet> wallets;
// Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we
// were downloading the block chain.
@@ -74,20 +76,45 @@
* one from scratch, or you can deserialize a saved wallet from disk using {@link Wallet#loadFromFile(java.io.File)}
* <p>
*
- * For the store you can use a {@link MemoryBlockStore} if you don't care about saving the downloaded data, or a
- * {@link BoundedOverheadBlockStore} if you'd like to ensure fast startup the next time you run the program.
+ * For the store you can use a {@link com.google.bitcoin.store.MemoryBlockStore} if you don't care about saving the downloaded data, or a
+ * {@link com.google.bitcoin.store.BoundedOverheadBlockStore} if you'd like to ensure fast startup the next time you run the program.
*/
public BlockChain(NetworkParameters params, Wallet wallet, BlockStore blockStore) {
+ this(params, new ArrayList<Wallet>(), blockStore);
+ if (wallet != null)
+ addWallet(wallet);
+ }
+
+ /**
+ * Constructs a BlockChain that has no wallet at all. This is helpful when you don't actually care about sending
+ * and receiving coins but rather, just want to explore the network data structures.
+ */
+ public BlockChain(NetworkParameters params, BlockStore blockStore) {
+ this(params, new ArrayList<Wallet>(), blockStore);
+ }
+
+ /**
+ * Constructs a BlockChain connected to the given list of wallets and a store.
+ */
+ public BlockChain(NetworkParameters params, List<Wallet> wallets, BlockStore blockStore){
try {
this.blockStore = blockStore;
chainHead = blockStore.getChainHead();
log.info("chain head is:\n{}", chainHead.getHeader());
} catch (BlockStoreException e) {
throw new RuntimeException(e);
}
-
this.params = params;
- this.wallet = wallet;
+ this.wallets = new ArrayList<Wallet>(wallets);
+ }
+
+ /**
+ * Add a wallet to the BlockChain. Note that the wallet will be unaffected by any blocks received while it
+ * was not part of this BlockChain. This method is useful if the wallet has just been created, and its keys
+ * have never been in use, or if the wallet has been loaded along with the BlockChain
+ */
+ public synchronized void addWallet(Wallet wallet) {
+ wallets.add(wallet);
}
/**
@@ -212,10 +239,12 @@ private void handleNewBestChain(StoredBlock newChainHead) throws BlockStoreExcep
// Then build a list of all blocks in the old part of the chain and the new part.
List<StoredBlock> oldBlocks = getPartialChain(chainHead, splitPoint);
List<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint);
- // Now inform the wallet. This is necessary so the set of currently active transactions (that we can spend)
+ // Now inform the wallets. This is necessary so the set of currently active transactions (that we can spend)
// can be updated to take into account the re-organize. We might also have received new coins we didn't have
// before and our previous spends might have been undone.
- wallet.reorganize(oldBlocks, newBlocks);
+ for (Wallet wallet : wallets) {
+ wallet.reorganize(oldBlocks, newBlocks);
+ }
// Update the pointer to the best known block.
setChainHead(newChainHead);
}
@@ -380,12 +409,33 @@ private void checkDifficultyTransitions(StoredBlock storedPrev, StoredBlock stor
receivedDifficulty.toString(16) + " vs " + newDifficulty.toString(16));
}
- private void scanTransaction(StoredBlock block, Transaction tx, NewBlockType blockType) throws ScriptException, VerificationException {
- // Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air).
- if (tx.isMine(wallet) && !tx.isCoinBase()) {
- System.out.println("======> RECEIVING"+tx.toString());
- wallet.receive(tx, block, blockType);
- }
+ private void scanTransaction(StoredBlock block, Transaction tx, NewBlockType blockType)
+ throws ScriptException, VerificationException {
+ for (Wallet wallet : wallets) {
+ boolean shouldReceive = false;
+ for (TransactionOutput output : tx.outputs) {
+ // TODO: Handle more types of outputs, not just regular to address outputs.
+ if (output.getScriptPubKey().isSentToIP()) return;
+ // This is not thread safe as a key could be removed between the call to isMine and receive.
+ if (output.isMine(wallet)) {
+ shouldReceive = true;
+ }
+ }
+
+ // Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air).
+ if (!tx.isCoinBase()) {
+ for (TransactionInput i : tx.inputs) {
+ byte[] pubkey = i.getScriptSig().getPubKey();
+ // This is not thread safe as a key could be removed between the call to isPubKeyMine and receive.
+ if (wallet.isPubKeyMine(pubkey)) {
+ shouldReceive = true;
+ }
+ }
+ }
+
+ if (shouldReceive)
+ wallet.receive(tx, block, blockType);
+ }
}
/**
View
16 src/com/google/bitcoin/core/GetBlocksMessage.java
@@ -22,10 +22,10 @@
public class GetBlocksMessage extends Message {
private static final long serialVersionUID = 3479412877853645644L;
- private final List<byte[]> locator;
- private final byte[] stopHash;
+ private final List<Sha256Hash> locator;
+ private final Sha256Hash stopHash;
- public GetBlocksMessage(NetworkParameters params, List<byte[]> locator, byte[] stopHash) {
+ public GetBlocksMessage(NetworkParameters params, List<Sha256Hash> locator, Sha256Hash stopHash) {
super(params);
this.locator = locator;
this.stopHash = stopHash;
@@ -37,8 +37,8 @@ public void parse() {
public String toString() {
StringBuffer b = new StringBuffer();
b.append("getblocks: ");
- for (byte[] hash : locator) {
- b.append(Utils.bytesToHexString(hash));
+ for (Sha256Hash hash : locator) {
+ b.append(hash.toString());
b.append(" ");
}
return b.toString();
@@ -53,12 +53,12 @@ public String toString() {
// identifiers that spans the entire chain with exponentially increasing gaps between
// them, until we end up at the genesis block. See CBlockLocator::Set()
buf.write(new VarInt(locator.size()).encode());
- for (byte[] hash : locator) {
+ for (Sha256Hash hash : locator) {
// Have to reverse as wire format is little endian.
- buf.write(Utils.reverseBytes(hash));
+ buf.write(Utils.reverseBytes(hash.getBytes()));
}
// Next, a block ID to stop at.
- buf.write(stopHash);
+ buf.write(stopHash.getBytes());
return buf.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
View
6 src/com/google/bitcoin/core/InventoryItem.java
@@ -24,15 +24,15 @@
}
public final Type type;
- public final byte[] hash;
+ public final Sha256Hash hash;
- public InventoryItem(Type type, byte[] hash) {
+ public InventoryItem(Type type, Sha256Hash hash) {
this.type = type;
this.hash = hash;
}
public String toString() {
- return type.toString() + ": " + Utils.bytesToHexString(hash);
+ return type.toString() + ": " + hash;
}
}
View
4 src/com/google/bitcoin/core/ListMessage.java
@@ -81,14 +81,14 @@ public void parse() throws ProtocolException {
@Override
- public void bitcoinSerializeToStream( OutputStream stream) throws IOException
+ public void bitcoinSerializeToStream(OutputStream stream) throws IOException
{
stream.write(new VarInt(items.size()).encode());
for (InventoryItem i : items) {
// Write out the type code.
Utils.uint32ToByteStreamLE(i.type.ordinal(), stream);
// And now the hash.
- stream.write(Utils.reverseBytes(i.hash));
+ stream.write(Utils.reverseBytes(i.hash.getBytes()));
}
}
}
View
87 src/com/google/bitcoin/core/MemoryBlockStore.java
@@ -1,87 +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.google.bitcoin.core;
-
-import java.io.*;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Keeps {@link StoredBlock}s in memory. Used primarily for unit testing.
- */
-public class MemoryBlockStore implements BlockStore {
- // We use a ByteBuffer to hold hashes here because the Java array equals()/hashcode() methods do not operate on
- // the contents of the array but just inherit the default Object behavior. ByteBuffer provides the functionality
- // needed to act as a key in a map.
- //
- // The StoredBlocks are also stored as serialized objects to ensure we don't have assumptions that would make
- // things harder for disk based implementations.
- private Map<ByteBuffer, byte[]> blockMap;
- private StoredBlock chainHead;
-
- public MemoryBlockStore(NetworkParameters params) {
- blockMap = new HashMap<ByteBuffer, byte[]>();
- // Insert the genesis block.
- try {
- Block genesisHeader = params.genesisBlock.cloneAsHeader();
- StoredBlock storedGenesis = new StoredBlock(genesisHeader, genesisHeader.getWork(), 0);
- put(storedGenesis);
- setChainHead(storedGenesis);
- } catch (BlockStoreException e) {
- throw new RuntimeException(e); // Cannot happen.
- } catch (VerificationException e) {
- throw new RuntimeException(e); // Cannot happen.
- }
- }
-
- public synchronized void put(StoredBlock block) throws BlockStoreException {
- byte[] hash = block.getHeader().getHash();
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try {
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(block);
- oos.close();
- blockMap.put(ByteBuffer.wrap(hash), bos.toByteArray());
- } catch (IOException e) {
- throw new BlockStoreException(e);
- }
- }
-
- public synchronized StoredBlock get(byte[] hash) throws BlockStoreException {
- try {
- byte[] serializedBlock = blockMap.get(ByteBuffer.wrap(hash));
- if (serializedBlock == null)
- return null;
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedBlock));
- StoredBlock storedBlock = (StoredBlock) ois.readObject();
- return storedBlock;
- } catch (IOException e) {
- throw new BlockStoreException(e);
- } catch (ClassNotFoundException e) {
- throw new BlockStoreException(e);
- }
- }
-
- public StoredBlock getChainHead() {
- return chainHead;
- }
-
- public void setChainHead(StoredBlock chainHead) throws BlockStoreException {
- this.chainHead = chainHead;
- }
-}
View
4 src/com/google/bitcoin/core/Message.java
@@ -113,14 +113,14 @@ long readUint32() {
return u;
}
- byte[] readHash() {
+ Sha256Hash readHash() {
byte[] hash = new byte[32];
System.arraycopy(bytes, cursor, hash, 0, 32);
// We have to flip it around, as it's been read off the wire in little endian.
// Not the most efficient way to do this but the clearest.
hash = Utils.reverseBytes(hash);
cursor += 32;
- return hash;
+ return new Sha256Hash(hash);
}
View
4 src/com/google/bitcoin/core/NetworkParameters.java
@@ -111,7 +111,7 @@ private static NetworkParameters createTestNet(NetworkParameters n) {
n.genesisBlock.setDifficultyTarget(0x1d07fff8L);
n.genesisBlock.setNonce(384568319);
String genesisHash = n.genesisBlock.getHashAsString();
- assert genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008");
+ assert genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008") : genesisHash;
return n;
}
@@ -141,7 +141,7 @@ public static NetworkParameters prodNet() {
}
/** Returns a testnet params modified to allow any difficulty target. */
- static NetworkParameters unitTests() {
+ public static NetworkParameters unitTests() {
NetworkParameters n = new NetworkParameters();
n = createTestNet(n);
n.proofOfWorkLimit = new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
View
18 src/com/google/bitcoin/core/Peer.java
@@ -134,7 +134,7 @@ private void processBlock(Block m) throws IOException {
synchronized (pendingGetBlockFutures) {
for (int i = 0; i < pendingGetBlockFutures.size(); i++) {
GetDataFuture<Block> f = pendingGetBlockFutures.get(i);
- if (Arrays.equals(f.getItem().hash, m.getHash())) {
+ if (f.getItem().hash.equals(m.getHash())) {
// Yes, it was. So pass it through the future.
f.setResult(m);
// Blocks explicitly requested don't get sent to the block chain.
@@ -180,10 +180,10 @@ private void processInv(InventoryMessage inv) throws IOException {
// chain, we may end up requesting blocks we already requested before. This shouldn't (in theory) happen
// enough to be a problem.
Block topBlock = blockChain.getUnconnectedBlock();
- byte[] topHash = (topBlock != null ? topBlock.getHash() : null);
+ Sha256Hash topHash = (topBlock != null ? topBlock.getHash() : null);
List<InventoryItem> items = inv.getItems();
if (items.size() == 1 && items.get(0).type == InventoryItem.Type.Block && topHash != null &&
- Arrays.equals(items.get(0).hash, topHash)) {
+ items.get(0).hash.equals(topHash)) {
// An inv with a single hash containing our most recent unconnected block is a special inv,
// it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to
// the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head
@@ -215,8 +215,8 @@ private void processInv(InventoryMessage inv) throws IOException {
* @param blockHash Hash of the block you wareare requesting.
* @throws IOException
*/
- public Future<Block> getBlock(byte[] blockHash) throws IOException {
- InventoryMessage getdata = new InventoryMessage(params);
+ public Future<Block> getBlock(Sha256Hash blockHash) throws IOException {
+ GetDataMessage getdata = new GetDataMessage(params);
InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash);
getdata.addItem(inventoryItem);
GetDataFuture<Block> future = new GetDataFuture<Block>(inventoryItem);
@@ -292,7 +292,7 @@ public void broadcastTransaction(Transaction tx) throws IOException {
conn.writeMessage(tx);
}
- private void blockChainDownload(byte[] toHash) throws IOException {
+ private void blockChainDownload(Sha256Hash toHash) throws IOException {
// This may run in ANY thread.
// The block chain download process is a bit complicated. Basically, we start with zero or more blocks in a
@@ -320,10 +320,10 @@ private void blockChainDownload(byte[] toHash) throws IOException {
//
// So this is a complicated process but it has the advantage that we can download a chain of enormous length
// in a relatively stateless manner and with constant/bounded memory usage.
- log.info("blockChainDownload({})", Utils.bytesToHexString(toHash));
+ log.info("blockChainDownload({})", toHash.toString());
// TODO: Block locators should be abstracted out rather than special cased here.
- List<byte[]> blockLocator = new LinkedList<byte[]>();
+ List<Sha256Hash> blockLocator = new LinkedList<Sha256Hash>();
// We don't do the exponential thinning here, so if we get onto a fork of the chain we will end up
// redownloading the whole thing again.
blockLocator.add(params.genesisBlock.getHash());
@@ -352,7 +352,7 @@ public CountDownLatch startBlockChainDownload() throws IOException {
chainCompletionLatch = new CountDownLatch(blocksToGet);
if (blocksToGet > 0) {
// When we just want as many blocks as possible, we can set the target hash to zero.
- blockChainDownload(new byte[32]);
+ blockChainDownload(Sha256Hash.ZERO_HASH);
}
return chainCompletionLatch;
}
View
7 src/com/google/bitcoin/core/Ping.java
@@ -16,12 +16,9 @@
package com.google.bitcoin.core;
-public class Ping
- extends Message
-{
+public class Ping extends Message {
@Override
- void parse() throws ProtocolException
- {
+ void parse() throws ProtocolException {
// nothing to parse
}
}
View
46 src/com/google/bitcoin/core/Sha256Hash.java
@@ -16,42 +16,66 @@
package com.google.bitcoin.core;
+import com.google.bitcoin.bouncycastle.util.encoders.Hex;
+
import java.io.Serializable;
+import java.math.BigInteger;
import java.util.Arrays;
-// TODO: Switch all code/interfaces to using this class.
-
/**
* A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be used as keys in a
* map. It also checks that the length is correct and provides a bit more type safety.
*/
public class Sha256Hash implements Serializable {
- public byte[] hash;
+ private byte[] bytes;
+
+ public static Sha256Hash ZERO_HASH = new Sha256Hash(new byte[32]);
- public Sha256Hash(byte[] hash) {
- assert hash.length == 32;
- this.hash = hash;
+ /** Creates a Sha256Hash by wrapping the given byte array. It must be 32 bytes long. */
+ public Sha256Hash(byte[] bytes) {
+ assert bytes.length == 32;
+ this.bytes = bytes;
+ }
+
+ /** Creates a Sha256Hash by decoding the given hex string. It must be 64 characters long. */
+ public Sha256Hash(String string) {
+ assert string.length() == 64;
+ this.bytes = Hex.decode(string);
}
/** Returns true if the hashes are equal. */
@Override
public boolean equals(Object other) {
if (!(other instanceof Sha256Hash)) return false;
- return Arrays.equals(hash, ((Sha256Hash)other).hash);
+ return Arrays.equals(bytes, ((Sha256Hash)other).bytes);
}
/**
* Hash code of the byte array as calculated by {@link Arrays#hashCode()}. Note the difference between a SHA256
- * secure hash and the type of quick/dirty hash used by the Java hashCode method which is designed for use in
- * hash tables.
+ * secure bytes and the type of quick/dirty bytes used by the Java hashCode method which is designed for use in
+ * bytes tables.
*/
@Override
public int hashCode() {
- return Arrays.hashCode(hash);
+ return Arrays.hashCode(bytes);
}
@Override
public String toString() {
- return Utils.bytesToHexString(hash);
+ return Utils.bytesToHexString(bytes);
+ }
+
+ /** Returns the bytes interpreted as a positive integer. */
+ public BigInteger toBigInteger() {
+ return new BigInteger(1, bytes);
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public Sha256Hash clone() {
+ return new Sha256Hash(bytes);
}
}
View
3 src/com/google/bitcoin/core/StoredBlock.java
@@ -16,6 +16,9 @@
package com.google.bitcoin.core;
+import com.google.bitcoin.store.BlockStore;
+import com.google.bitcoin.store.BlockStoreException;
+
import java.io.Serializable;
import java.math.BigInteger;
View
6 src/com/google/bitcoin/core/Transaction.java
@@ -197,6 +197,10 @@ public BigInteger getValueSentFromMe(Wallet wallet) throws ScriptException {
connected = input.getConnectedOutput(wallet.pending);
if (connected == null)
continue;
+ // The connected output may be the change to the sender of a previous input sent to this wallet. In this
+ // case we ignore it.
+ if (!connected.isMine(wallet))
+ continue;
v = v.add(connected.getValue());
}
return v;
@@ -501,6 +505,6 @@ public boolean equals(Object other) {
@Override
public int hashCode() {
- return Arrays.hashCode(getHash().hash);
+ return getHash().hashCode();
}
}
View
10 src/com/google/bitcoin/core/TransactionInput.java
@@ -93,9 +93,7 @@ public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
* Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true.
*/
public boolean isCoinBase() {
- for (int i = 0; i < outpoint.hash.length; i++)
- if (outpoint.hash[i] != 0) return false;
- return true;
+ return outpoint.hash.equals(Sha256Hash.ZERO_HASH);
}
/**
@@ -157,8 +155,7 @@ public String toString() {
* @return The TransactionOutput or null if the transactions map doesn't contain the referenced tx.
*/
TransactionOutput getConnectedOutput(Map<Sha256Hash, Transaction> transactions) {
- Sha256Hash h = new Sha256Hash(outpoint.hash);
- Transaction tx = transactions.get(h);
+ Transaction tx = transactions.get(outpoint.hash);
if (tx == null)
return null;
TransactionOutput out = tx.outputs.get((int)outpoint.index);
@@ -174,8 +171,7 @@ TransactionOutput getConnectedOutput(Map<Sha256Hash, Transaction> transactions)
* @return true if connection took place, false if the referenced transaction was not in the list.
*/
ConnectionResult connect(Map<Sha256Hash, Transaction> transactions, boolean disconnect) {
- Sha256Hash h = new Sha256Hash(outpoint.hash);
- Transaction tx = transactions.get(h);
+ Transaction tx = transactions.get(outpoint.hash);
if (tx == null)
return TransactionInput.ConnectionResult.NO_SUCH_TX;
TransactionOutput out = tx.outputs.get((int)outpoint.index);
View
9 src/com/google/bitcoin/core/TransactionOutPoint.java
@@ -29,7 +29,7 @@
private static final long serialVersionUID = -6320880638344662579L;
/** Hash of the transaction to which we refer. */
- byte[] hash;
+ Sha256Hash hash;
/** Which output of that transaction we are talking about. */
long index;
@@ -41,11 +41,11 @@
super(params);
this.index = index;
if (fromTx != null) {
- this.hash = fromTx.getHash().hash;
+ this.hash = fromTx.getHash();
this.fromTx = fromTx;
} else {
// This happens when constructing the genesis block.
- hash = new byte[32]; // All zeros.
+ hash = Sha256Hash.ZERO_HASH;
}
}
@@ -62,8 +62,7 @@ void parse() throws ProtocolException {
@Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
- assert hash.length == 32;
- stream.write(Utils.reverseBytes(hash));
+ stream.write(Utils.reverseBytes(hash.getBytes()));
Utils.uint32ToByteStreamLE(index, stream);
}
View
2 src/com/google/bitcoin/core/VersionMessage.java
@@ -64,7 +64,7 @@ public VersionMessage(NetworkParameters params, int newBestHeight) {
} catch (UnknownHostException e) {
throw new RuntimeException(e); // Cannot happen.
}
- subVer = "BitCoinJ 0.2";
+ subVer = "BitCoinJ 0.3-SNAPSHOT";
bestHeight = newBestHeight;
}
View
13 src/com/google/bitcoin/core/Wallet.java
@@ -354,7 +354,10 @@ private void updateForSpends(Transaction tx) throws VerificationException {
// A -> spent by B [pending]
// \-> spent by C [chain]
Transaction doubleSpent = input.outpoint.fromTx; // == A
- Transaction connected = doubleSpent.outputs.get((int)input.outpoint.index).getSpentBy().parentTransaction;
+ int index = (int) input.outpoint.index;
+ TransactionOutput output = doubleSpent.outputs.get(index);
+ TransactionInput spentBy = output.getSpentBy();
+ Transaction connected = spentBy.parentTransaction;
if (pending.containsKey(connected.getHash())) {
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
log.info(" <-pending ->dead");
@@ -399,6 +402,14 @@ public synchronized void addEventListener(WalletEventListener listener) {
}
/**
+ * Removes the given event listener object. Returns true if the listener was removed,
+ * false if that listener was never added.
+ */
+ public synchronized boolean removeEventListener(WalletEventListener listener) {
+ return eventListeners.remove(listener);
+ }
+
+ /**
* Call this when we have successfully transmitted the send tx to the network, to update the wallet.
*/
public synchronized void confirmSend(Transaction tx) {
View
11 ...com/google/bitcoin/core/DnsDiscovery.java → ...oogle/bitcoin/discovery/DnsDiscovery.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
+import com.google.bitcoin.core.NetworkParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.*;
import java.util.HashSet;
import java.util.Set;
-import java.util.Vector;
/**
* Supports peer discovery through DNS.<p>
@@ -38,7 +38,12 @@
private String[] hostNames;
private NetworkParameters netParams;
- private static final String[] defaultHosts = new String[] {"bitseed.xf2.org","bitseed.bitcoin.org.uk"};
+
+ public static final String[] defaultHosts = new String[] {
+ "dnsseed.bluematt.me", // Auto generated
+ "bitseed.xf2.org", // Static
+ "bitseed.bitcoin.org.uk" // Static
+ };
/**
* Supports finding peers through DNS A records. Community run DNS entry points will be used.
View
5 ...com/google/bitcoin/core/IrcDiscovery.java → ...oogle/bitcoin/discovery/IrcDiscovery.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
+import com.google.bitcoin.core.AddressFormatException;
+import com.google.bitcoin.core.Base58;
+import com.google.bitcoin.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
View
2 ...om/google/bitcoin/core/PeerDiscovery.java → ...ogle/bitcoin/discovery/PeerDiscovery.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
import java.net.InetSocketAddress;
View
2 .../bitcoin/core/PeerDiscoveryException.java → ...oin/discovery/PeerDiscoveryException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
public class PeerDiscoveryException extends Exception {
View
8 src/com/google/bitcoin/core/SeedPeers.java → ...m/google/bitcoin/discovery/SeedPeers.java
@@ -13,7 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
+
+import com.google.bitcoin.core.NetworkParameters;
+import com.google.bitcoin.discovery.PeerDiscovery;
+import com.google.bitcoin.discovery.PeerDiscoveryException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -79,7 +83,7 @@ private InetAddress convertAddress(int seed) throws UnknownHostException{
return InetAddress.getByAddress(v4addr);
}
- static int[] seedAddrs =
+ public static int[] seedAddrs =
{
0x1ddb1032, 0x6242ce40, 0x52d6a445, 0x2dd7a445, 0x8a53cd47, 0x73263750, 0xda23c257, 0xecd4ed57,
0x0a40ec59, 0x75dce160, 0x7df76791, 0x89370bad, 0xa4f214ad, 0x767700ae, 0x638b0418, 0x868a1018,
View
46 src/com/google/bitcoin/examples/FetchBlock.java
@@ -0,0 +1,46 @@
+/*
+ * 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.google.bitcoin.examples;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.store.BlockStore;
+import com.google.bitcoin.store.MemoryBlockStore;
+
+import java.net.InetAddress;
+import java.util.concurrent.Future;
+
+/**
+ * Downloads the block given a block hash from the localhost node and prints it out.
+ */
+public class FetchBlock {
+ public static void main(String[] args) throws Exception {
+ System.out.println("Connecting to node");
+ final NetworkParameters params = NetworkParameters.prodNet();
+ NetworkConnection conn = new NetworkConnection(InetAddress.getLocalHost(), params, 0, 60000);
+ BlockStore blockStore = new MemoryBlockStore(params);
+ BlockChain chain = new BlockChain(params, blockStore);
+ Peer peer = new Peer(params, conn, chain);
+ peer.start();
+
+ Sha256Hash blockHash = new Sha256Hash(args[0]);
+ Future<Block> future = peer.getBlock(blockHash);
+ System.out.println("Waiting for node to send us the requested block: " + blockHash);
+ Block block = future.get();
+ System.out.println(block);
+ peer.disconnect();
+ }
+}
View
2 src/com/google/bitcoin/examples/PingService.java
@@ -17,6 +17,8 @@
package com.google.bitcoin.examples;
import com.google.bitcoin.core.*;
+import com.google.bitcoin.store.BlockStore;
+import com.google.bitcoin.store.BoundedOverheadBlockStore;
import java.io.File;
import java.io.IOException;
View
3 src/com/google/bitcoin/examples/PrintPeers.java
@@ -19,6 +19,9 @@
import java.net.InetSocketAddress;
import com.google.bitcoin.core.*;
+import com.google.bitcoin.discovery.DnsDiscovery;
+import com.google.bitcoin.discovery.IrcDiscovery;
+import com.google.bitcoin.discovery.PeerDiscoveryException;
/**
* Prints a list of IP addresses connected to the rendezvous point on the LFnet IRC channel.
View
1 src/com/google/bitcoin/examples/PrivateKeys.java
@@ -17,6 +17,7 @@
package com.google.bitcoin.examples;
import com.google.bitcoin.core.*;
+import com.google.bitcoin.store.MemoryBlockStore;
import java.math.BigInteger;
import java.net.InetAddress;
View
1 src/com/google/bitcoin/examples/RefreshWallet.java
@@ -17,6 +17,7 @@
package com.google.bitcoin.examples;
import com.google.bitcoin.core.*;
+import com.google.bitcoin.store.*;
import java.io.File;
import java.math.BigInteger;
View
7 src/com/google/bitcoin/core/BlockStore.java → src/com/google/bitcoin/store/BlockStore.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
+
+import com.google.bitcoin.core.Sha256Hash;
+import com.google.bitcoin.core.StoredBlock;
/**
* An implementor of BlockStore saves StoredBlock objects to disk. Different implementations store them in
@@ -38,7 +41,7 @@
* Returns the StoredBlock given a hash. The returned values block.getHash() method will be equal to the
* parameter. If no such block is found, returns null.
*/
- StoredBlock get(byte[] hash) throws BlockStoreException;
+ StoredBlock get(Sha256Hash hash) throws BlockStoreException;
/**
* Returns the {@link StoredBlock} that represents the top of the chain of greatest total work.
View
2 ...gle/bitcoin/core/BlockStoreException.java → ...le/bitcoin/store/BlockStoreException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
/**
* Thrown when something goes wrong with storing a block. Examples: out of disk space.
View
23 ...tcoin/core/BoundedOverheadBlockStore.java → ...coin/store/BoundedOverheadBlockStore.java
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*;
+import com.google.bitcoin.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,7 +52,7 @@
private LinkedHashMap<Sha256Hash, StoredBlock> blockCache = new LinkedHashMap<Sha256Hash, StoredBlock>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Sha256Hash, StoredBlock> entry) {
- return size() > 100; // This was chosen arbitrarily.
+ return size() > 2050; // Slightly more than the difficulty transition period.
}
};
// Use a separate cache to track get() misses. This is to efficiently handle the case of an unconnected block
@@ -165,8 +166,8 @@ private void createNewStore(NetworkParameters params, File file) throws BlockSto
// Set up the genesis block. When we start out fresh, it is by definition the top of the chain.
Block genesis = params.genesisBlock.cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
- this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash());
- this.file.write(this.chainHead.hash);
+ this.chainHead = storedGenesis.getHeader().getHash();
+ this.file.write(this.chainHead.getBytes());
put(storedGenesis);
} catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen.
@@ -201,7 +202,7 @@ private void load(File file) throws IOException, BlockStoreException {
public synchronized void put(StoredBlock block) throws BlockStoreException {
try {
- Sha256Hash hash = new Sha256Hash(block.getHeader().getHash());
+ Sha256Hash hash = block.getHeader().getHash();
// Append to the end of the file.
dummyRecord.write(channel, block);
blockCache.put(hash, block);
@@ -210,9 +211,8 @@ public synchronized void put(StoredBlock block) throws BlockStoreException {
}
}
- public synchronized StoredBlock get(byte[] hashBytes) throws BlockStoreException {
+ public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
// Check the memory cache first.
- Sha256Hash hash = new Sha256Hash(hashBytes);
StoredBlock fromMem = blockCache.get(hash);
if (fromMem != null) {
return fromMem;
@@ -247,7 +247,7 @@ private Record getRecord(Sha256Hash hash) throws BlockStoreException, IOExceptio
do {
if (!record.read(channel, pos, buf))
throw new IOException("Failed to read buffer");
- if (Arrays.equals(record.getHeader(params).getHash(), hash.hash)) {
+ if (record.getHeader(params).getHash().equals(hash)) {
// Found it. Update file position for next time.
channel.position(pos);
return record;
@@ -268,15 +268,14 @@ private Record getRecord(Sha256Hash hash) throws BlockStoreException, IOExceptio
}
public synchronized StoredBlock getChainHead() throws BlockStoreException {
- return get(chainHead.hash);
+ return get(chainHead);
}
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
try {
- byte[] hash = chainHead.getHeader().getHash();
- this.chainHead = new Sha256Hash(hash);
+ this.chainHead = chainHead.getHeader().getHash();
// Write out new hash to the first 32 bytes of the file past one (first byte is version number).
- channel.write(ByteBuffer.wrap(hash), 1);
+ channel.write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) {
throw new BlockStoreException(e);
}
View
24 ...m/google/bitcoin/core/DiskBlockStore.java → .../google/bitcoin/store/DiskBlockStore.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
+import com.google.bitcoin.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,8 +63,8 @@ private void createNewStore(NetworkParameters params, File file) throws BlockSto
// Set up the genesis block. When we start out fresh, it is by definition the top of the chain.
Block genesis = params.genesisBlock.cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
- this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash());
- stream.write(this.chainHead.hash);
+ this.chainHead = storedGenesis.getHeader().getHash();
+ stream.write(this.chainHead.getBytes());
put(storedGenesis);
} catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen.
@@ -109,8 +110,8 @@ private void load(File file) throws IOException, BlockStoreException {
if (b.equals(params.genesisBlock)) {
s = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
} else {
- throw new BlockStoreException("Could not connect " + Utils.bytesToHexString(b.getHash()) + " to "
- + Utils.bytesToHexString(b.getPrevBlockHash()));
+ throw new BlockStoreException("Could not connect " + b.getHash().toString() + " to "
+ + b.getPrevBlockHash().toString());
}
} else {
// Don't try to verify the genesis block to avoid upsetting the unit tests.
@@ -119,7 +120,7 @@ private void load(File file) throws IOException, BlockStoreException {
s = prev.build(b);
}
// Save in memory.
- blockMap.put(new Sha256Hash(b.getHash()), s);
+ blockMap.put(b.getHash(), s);
}
} catch (ProtocolException e) {
// Corrupted file.
@@ -134,7 +135,7 @@ private void load(File file) throws IOException, BlockStoreException {
public synchronized void put(StoredBlock block) throws BlockStoreException {
try {
- Sha256Hash hash = new Sha256Hash(block.getHeader().getHash());
+ Sha256Hash hash = block.getHeader().getHash();
assert blockMap.get(hash) == null : "Attempt to insert duplicate";
// Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded.
byte[] bytes = block.getHeader().bitcoinSerialize();
@@ -146,8 +147,8 @@ public synchronized void put(StoredBlock block) throws BlockStoreException {
}
}
- public synchronized StoredBlock get(byte[] hash) throws BlockStoreException {
- return blockMap.get(new Sha256Hash(hash));
+ public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
+ return blockMap.get(hash);
}
public synchronized StoredBlock getChainHead() throws BlockStoreException {
@@ -156,10 +157,9 @@ public synchronized StoredBlock getChainHead() throws BlockStoreException {
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
try {
- byte[] hash = chainHead.getHeader().getHash();
- this.chainHead = new Sha256Hash(hash);
+ this.chainHead = chainHead.getHeader().getHash();
// Write out new hash to the first 32 bytes of the file past one (first byte is version number).
- stream.getChannel().write(ByteBuffer.wrap(hash), 1);
+ stream.getChannel().write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) {
throw new BlockStoreException(e);
}
View
64 src/com/google/bitcoin/store/MemoryBlockStore.java
@@ -0,0 +1,64 @@
+/**
+ * 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.google.bitcoin.store;
+
+import com.google.bitcoin.core.*;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Keeps {@link com.google.bitcoin.core.StoredBlock}s in memory. Used primarily for unit testing.
+ */
+public class MemoryBlockStore implements BlockStore {
+ private Map<Sha256Hash, StoredBlock> blockMap;
+ private StoredBlock chainHead;
+
+ public MemoryBlockStore(NetworkParameters params) {
+ blockMap = new HashMap<Sha256Hash, StoredBlock>();
+ // Insert the genesis block.
+ try {
+ Block genesisHeader = params.genesisBlock.cloneAsHeader();
+ StoredBlock storedGenesis = new StoredBlock(genesisHeader, genesisHeader.getWork(), 0);
+ put(storedGenesis);
+ setChainHead(storedGenesis);
+ } catch (BlockStoreException e) {
+ throw new RuntimeException(e); // Cannot happen.
+ } catch (VerificationException e) {
+ throw new RuntimeException(e); // Cannot happen.
+ }
+ }
+
+ public synchronized void put(StoredBlock block) throws BlockStoreException {
+ Sha256Hash hash = block.getHeader().getHash();
+ blockMap.put(hash, block);
+ }
+
+ public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
+ return blockMap.get(hash);
+ }
+
+ public StoredBlock getChainHead() {
+ return chainHead;
+ }
+
+ public void setChainHead(StoredBlock chainHead) throws BlockStoreException {
+ this.chainHead = chainHead;
+ }
+}
View
11 tests/com/google/bitcoin/core/BlockChainTest.java
@@ -17,6 +17,7 @@
package com.google.bitcoin.core;
import com.google.bitcoin.bouncycastle.util.encoders.Hex;
+import com.google.bitcoin.store.MemoryBlockStore;
import org.junit.Before;
import org.junit.Test;
@@ -122,7 +123,7 @@ public void testBadDifficulty() throws Exception {
NetworkParameters params2 = NetworkParameters.testNet();
Block bad = new Block(params2);
// Merkle root can be anything here, doesn't matter.
- bad.setMerkleRoot(Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+ 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);
@@ -157,21 +158,21 @@ public void testBadDifficulty() throws Exception {
// Some blocks from the test net.
private Block getBlock2() throws Exception {
Block b2 = new Block(testNet);
- b2.setMerkleRoot(Hex.decode("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
+ b2.setMerkleRoot(new Sha256Hash("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
b2.setNonce(2642058077L);
b2.setTime(1296734343L);
- b2.setPrevBlockHash(Hex.decode("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604"));
+ b2.setPrevBlockHash(new Sha256Hash("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604"));
assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString());
b2.verify();
return b2;
}
private Block getBlock1() throws Exception {
Block b1 = new Block(testNet);
- b1.setMerkleRoot(Hex.decode("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
+ b1.setMerkleRoot(new Sha256Hash("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
b1.setNonce(236038445);
b1.setTime(1296734340);
- b1.setPrevBlockHash(Hex.decode("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"));
+ b1.setPrevBlockHash(new Sha256Hash("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"));
assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString());
b1.verify();
return b1;
View
1 tests/com/google/bitcoin/core/ChainSplitTests.java
@@ -16,6 +16,7 @@
package com.google.bitcoin.core;
+import com.google.bitcoin.store.MemoryBlockStore;
import org.junit.Before;
import org.junit.Test;
View
37 tests/com/google/bitcoin/core/WalletTest.java
@@ -16,6 +16,9 @@
package com.google.bitcoin.core;
+import com.google.bitcoin.store.BlockStore;
+import com.google.bitcoin.store.BlockStoreException;
+import com.google.bitcoin.store.MemoryBlockStore;
import org.junit.Before;
import org.junit.Test;
@@ -32,10 +35,11 @@
private Address myAddress;
private Wallet wallet;
private BlockStore blockStore;
+ private ECKey myKey;
@Before
public void setUp() throws Exception {
- ECKey myKey = new ECKey();
+ myKey = new ECKey();
myAddress = myKey.toAddress(params);
wallet = new Wallet(params);
wallet.addKey(myKey);
@@ -82,7 +86,7 @@ private BlockPair createFakeBlock(Transaction... transactions) {
}
@Test
- public void testBasicSpending() throws Exception {
+ public void basicSpending() throws Exception {
// We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change.
BigInteger v1 = Utils.toNanoCoins(1, 0);
Transaction t1 = createFakeTx(v1, myAddress);
@@ -102,7 +106,7 @@ public void testBasicSpending() throws Exception {
}
@Test
- public void testSideChain() throws Exception {
+ public void sideChain() throws Exception {
// The wallet receives a coin on the main chain, then on a side chain. Only main chain counts towards balance.
BigInteger v1 = Utils.toNanoCoins(1, 0);
Transaction t1 = createFakeTx(v1, myAddress);
@@ -118,7 +122,7 @@ public void testSideChain() throws Exception {
}
@Test
- public void testListener() throws Exception {
+ public void listeners() throws Exception {
final Transaction fakeTx = createFakeTx(Utils.toNanoCoins(1, 0), myAddress);
final boolean[] didRun = new boolean[1];
WalletEventListener listener = new WalletEventListener() {
@@ -136,7 +140,7 @@ public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, Bi
}
@Test
- public void testBalance() throws Exception {
+ public void balance() throws Exception {
// Receive 5 coins then half a coin.
BigInteger v1 = toNanoCoins(5, 0);
BigInteger v2 = toNanoCoins(0, 50);
@@ -175,7 +179,7 @@ public void testBalance() throws Exception {
// suite.
@Test
- public void testBlockChainCatchup() throws Exception {
+ public void blockChainCatchup() throws Exception {
Transaction tx1 = createFakeTx(Utils.toNanoCoins(1, 0), myAddress);
StoredBlock b1 = createFakeBlock(tx1).storedBlock;
wallet.receive(tx1, b1, BlockChain.NewBlockType.BEST_CHAIN);
@@ -197,7 +201,7 @@ public void testBlockChainCatchup() throws Exception {
}
@Test
- public void testBalances() throws Exception {
+ public void balances() throws Exception {
BigInteger nanos = Utils.toNanoCoins(1, 0);
Transaction tx1 = createFakeTx(nanos, myAddress);
wallet.receive(tx1, null, BlockChain.NewBlockType.BEST_CHAIN);
@@ -210,7 +214,24 @@ public void testBalances() throws Exception {
}
@Test
- public void testFinneyAttack() throws Exception {
+ public void transactions() throws Exception {
+ // This test covers a bug in which Transaction.getValueSentFromMe was calculating incorrectly.
+ Transaction tx = createFakeTx(Utils.toNanoCoins(1, 0), myAddress);
+ // Now add another output (ie, change) that goes to some other address.
+ Address someOtherGuy = new ECKey().toAddress(params);
+ TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
+ tx.addOutput(output);
+ wallet.receive(tx, null, BlockChain.NewBlockType.BEST_CHAIN);
+ // Now the other guy creates a transaction which spends that change.
+ Transaction tx2 = new Transaction(params);
+ tx2.addInput(output);
+ tx2.addOutput(new TransactionOutput(params, tx2, Utils.toNanoCoins(0, 5), myAddress));
+ // tx2 doesn't send any coins from us, even though the output is in the wallet.
+ assertEquals(Utils.toNanoCoins(0, 0), tx2.getValueSentFromMe(wallet));
+ }
+
+ @Test
+ public void finneyAttack() throws Exception {
// A Finney attack is where a miner includes a transaction spending coins to themselves but does not
// broadcast it. When they find a solved block, they hold it back temporarily whilst they buy something with
// those same coins. After purchasing, they broadcast the block thus reversing the transaction. It can be
View
8 ...google/bitcoin/core/IrcDiscoveryTest.java → ...e/bitcoin/discovery/IrcDiscoveryTest.java
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
import static org.junit.Assert.*;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import com.google.bitcoin.discovery.IrcDiscovery;
import org.junit.Test;
public class IrcDiscoveryTest {
@@ -41,8 +42,9 @@ public void testParseUserList() throws UnknownHostException {
InetSocketAddress[] decoded = addresses.toArray(new InetSocketAddress[]{});
for (int i = 0; i < decoded.length; i++) {
- String formattedIP = decoded[0].getAddress().getHostAddress() + ":" + ((Integer)decoded[i].getPort()).toString();
- assertEquals("IPs decoded improperly", ips[0], formattedIP);
+ String formattedIP = decoded[i].getAddress().getHostAddress() + ":" + ((Integer)decoded[i].getPort())
+ .toString();
+ assertEquals("IPs decoded improperly", ips[i], formattedIP);
}
}
View
5 ...om/google/bitcoin/core/SeedPeersTest.java → ...ogle/bitcoin/discovery/SeedPeersTest.java
@@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.discovery;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
+
+import com.google.bitcoin.core.NetworkParameters;
+import com.google.bitcoin.discovery.SeedPeers;
import org.junit.Test;
import java.net.InetSocketAddress;
View
7 ...n/core/BoundedOverheadBlockStoreTest.java → .../store/BoundedOverheadBlockStoreTest.java
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
+import com.google.bitcoin.core.Address;
+import com.google.bitcoin.core.ECKey;
+import com.google.bitcoin.core.NetworkParameters;
+import com.google.bitcoin.core.StoredBlock;
+import com.google.bitcoin.store.BoundedOverheadBlockStore;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
View
8 ...ogle/bitcoin/core/DiskBlockStoreTest.java → ...gle/bitcoin/store/DiskBlockStoreTest.java
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.bitcoin.core;
+package com.google.bitcoin.store;
+import com.google.bitcoin.core.Address;
+import com.google.bitcoin.core.ECKey;
+import com.google.bitcoin.core.NetworkParameters;
+import com.google.bitcoin.core.StoredBlock;
+import com.google.bitcoin.store.DiskBlockStore;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
@@ -24,7 +29,6 @@
public void testStorage() throws Exception {
File temp = File.createTempFile("bitcoinj-test", null, null);
System.out.println(temp.getAbsolutePath());
- //temp.deleteOnExit();
NetworkParameters params = NetworkParameters.unitTests();
Address to = new ECKey().toAddress(params);

0 comments on commit af7a570

Please sign in to comment.
Something went wrong with that request. Please try again.