diff --git a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java
index 6107cf75..f272b68e 100644
--- a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java
+++ b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java
@@ -149,7 +149,51 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
return null;
}
}
-
+
+ /**
+ * Get the {@link Script} from the script bytes.
+ * @param scriptBytes The script bytes.
+ * @return The script.
+ */
+ @Nullable
+ private Script getScript(byte[] scriptBytes) {
+ Script script = null;
+ try {
+ script = new Script(scriptBytes);
+ } catch (Exception e) {
+ log.warn("Unable to parse script");
+ }
+ return script;
+ }
+
+ /**
+ * Get the address from the {@link Script} if it exists otherwise return empty string "".
+ * @param script The script.
+ * @return The address.
+ */
+ private String getScriptAddress(@Nullable Script script) {
+ String address = "";
+ try {
+ if (script != null) {
+ address = script.getToAddress(params, true).toString();
+ }
+ } catch (Exception e) {
+ }
+ return address;
+ }
+
+ /**
+ * Get the {@link Script.ScriptType} of this script.
+ * @param script The script.
+ * @return The script type.
+ */
+ private Script.ScriptType getScriptType(@Nullable Script script) {
+ if (script != null) {
+ return script.getScriptType();
+ }
+ return Script.ScriptType.NO_TYPE;
+ }
+
@Override
protected TransactionOutputChanges connectTransactions(int height, Block block)
throws VerificationException, BlockStoreException {
@@ -161,8 +205,8 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
blockStore.beginDatabaseBatchWrite();
- LinkedList txOutsSpent = new LinkedList();
- LinkedList txOutsCreated = new LinkedList();
+ LinkedList txOutsSpent = new LinkedList();
+ LinkedList txOutsCreated = new LinkedList();
long sigOps = 0;
final Set verifyFlags = EnumSet.noneOf(VerifyFlag.class);
if (block.getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME)
@@ -199,7 +243,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
// outputs.
for (int index = 0; index < tx.getInputs().size(); index++) {
TransactionInput in = tx.getInputs().get(index);
- StoredTransactionOutput prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
+ UTXO prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
in.getOutpoint().getIndex());
if (prevOut == null)
throw new VerificationException("Attempted to spend a non-existent or already spent output!");
@@ -232,8 +276,14 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
for (TransactionOutput out : tx.getOutputs()) {
valueOut = valueOut.add(out.getValue());
// For each output, add it to the set of unspent outputs so it can be consumed in future.
- StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
- height, isCoinBase, out.getScriptBytes());
+ Script script = getScript(out.getScriptBytes());
+ UTXO newOut = new UTXO(hash,
+ out.getIndex(),
+ out.getValue(),
+ height, isCoinBase,
+ out.getScriptBytes(),
+ getScriptAddress(script),
+ getScriptType(script).ordinal());
blockStore.addUnspentTransactionOutput(newOut);
txOutsCreated.add(newOut);
}
@@ -304,8 +354,8 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
try {
List transactions = block.getTransactions();
if (transactions != null) {
- LinkedList txOutsSpent = new LinkedList();
- LinkedList txOutsCreated = new LinkedList();
+ LinkedList txOutsSpent = new LinkedList();
+ LinkedList txOutsCreated = new LinkedList();
long sigOps = 0;
final Set verifyFlags = EnumSet.noneOf(VerifyFlag.class);
if (newBlock.getHeader().getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME)
@@ -331,7 +381,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
if (!isCoinBase) {
for (int index = 0; index < tx.getInputs().size(); index++) {
final TransactionInput in = tx.getInputs().get(index);
- final StoredTransactionOutput prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
+ final UTXO prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
in.getOutpoint().getIndex());
if (prevOut == null)
throw new VerificationException("Attempted spend of a non-existent or already spent output!");
@@ -355,9 +405,15 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
Sha256Hash hash = tx.getHash();
for (TransactionOutput out : tx.getOutputs()) {
valueOut = valueOut.add(out.getValue());
- StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
- newBlock.getHeight(), isCoinBase,
- out.getScriptBytes());
+ Script script = getScript(out.getScriptBytes());
+ UTXO newOut = new UTXO(hash,
+ out.getIndex(),
+ out.getValue(),
+ newBlock.getHeight(),
+ isCoinBase,
+ out.getScriptBytes(),
+ getScriptAddress(script),
+ getScriptType(script).ordinal());
blockStore.addUnspentTransactionOutput(newOut);
txOutsCreated.add(newOut);
}
@@ -400,14 +456,14 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
} else {
txOutChanges = block.getTxOutChanges();
if (!params.isCheckpoint(newBlock.getHeight()))
- for(StoredTransactionOutput out : txOutChanges.txOutsCreated) {
+ for(UTXO out : txOutChanges.txOutsCreated) {
Sha256Hash hash = out.getHash();
if (blockStore.getTransactionOutput(hash, out.getIndex()) != null)
throw new VerificationException("Block failed BIP30 test!");
}
- for (StoredTransactionOutput out : txOutChanges.txOutsCreated)
+ for (UTXO out : txOutChanges.txOutsCreated)
blockStore.addUnspentTransactionOutput(out);
- for (StoredTransactionOutput out : txOutChanges.txOutsSpent)
+ for (UTXO out : txOutChanges.txOutsSpent)
blockStore.removeUnspentTransactionOutput(out);
}
} catch (VerificationException e) {
@@ -434,9 +490,9 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash());
if (undoBlock == null) throw new PrunedException(oldBlock.getHeader().getHash());
TransactionOutputChanges txOutChanges = undoBlock.getTxOutChanges();
- for(StoredTransactionOutput out : txOutChanges.txOutsSpent)
+ for(UTXO out : txOutChanges.txOutsSpent)
blockStore.addUnspentTransactionOutput(out);
- for(StoredTransactionOutput out : txOutChanges.txOutsCreated)
+ for(UTXO out : txOutChanges.txOutsCreated)
blockStore.removeUnspentTransactionOutput(out);
} catch (PrunedException e) {
blockStore.abortDatabaseBatchWrite();
diff --git a/core/src/main/java/org/bitcoinj/core/TransactionInput.java b/core/src/main/java/org/bitcoinj/core/TransactionInput.java
index ac41d0b8..92930bd6 100644
--- a/core/src/main/java/org/bitcoinj/core/TransactionInput.java
+++ b/core/src/main/java/org/bitcoinj/core/TransactionInput.java
@@ -91,7 +91,11 @@ public class TransactionInput extends ChildMessage implements Serializable {
TransactionInput(NetworkParameters params, Transaction parentTransaction, TransactionOutput output) {
super(params);
long outputIndex = output.getIndex();
- outpoint = new TransactionOutPoint(params, outputIndex, output.getParentTransaction());
+ if(output.getParentTransaction() != null ) {
+ outpoint = new TransactionOutPoint(params, outputIndex, output.getParentTransaction());
+ } else {
+ outpoint = new TransactionOutPoint(params, output);
+ }
scriptBytes = EMPTY_ARRAY;
sequence = NO_SEQUENCE;
setParent(parentTransaction);
diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
index 12677210..b9d39791 100644
--- a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
+++ b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
@@ -46,6 +46,9 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
// It points to the connected transaction.
Transaction fromTx;
+ // The connected output.
+ private TransactionOutput connectedOutput;
+
public TransactionOutPoint(NetworkParameters params, long index, @Nullable Transaction fromTx) {
super(params);
this.index = index;
@@ -66,6 +69,11 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
length = MESSAGE_LENGTH;
}
+ public TransactionOutPoint(NetworkParameters params, TransactionOutput connectedOutput) {
+ this(params, connectedOutput.getIndex(), connectedOutput.getParentTransactionHash());
+ this.connectedOutput = connectedOutput;
+ }
+
/**
/**
* Deserializes the message. This is usually part of a transaction message.
@@ -120,8 +128,12 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
*/
@Nullable
public TransactionOutput getConnectedOutput() {
- if (fromTx == null) return null;
- return fromTx.getOutputs().get((int) index);
+ if (fromTx != null) {
+ return fromTx.getOutputs().get((int) index);
+ } else if (connectedOutput != null) {
+ return connectedOutput;
+ }
+ return null;
}
/**
diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutput.java b/core/src/main/java/org/bitcoinj/core/TransactionOutput.java
index 854a4461..27f8d6f7 100644
--- a/core/src/main/java/org/bitcoinj/core/TransactionOutput.java
+++ b/core/src/main/java/org/bitcoinj/core/TransactionOutput.java
@@ -91,7 +91,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
/**
* Creates an output that sends 'value' to the given address (public key hash). The amount should be created with
- * something like {@link Utils#valueOf(int, int)}. Typically you would use
+ * something like {@link Coin#valueOf(int, int)}. Typically you would use
* {@link Transaction#addOutput(Coin, Address)} instead of creating a TransactionOutput directly.
*/
public TransactionOutput(NetworkParameters params, @Nullable Transaction parent, Coin value, Address to) {
@@ -100,7 +100,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
/**
* Creates an output that sends 'value' to the given public key using a simple CHECKSIG script (no addresses). The
- * amount should be created with something like {@link Utils#valueOf(int, int)}. Typically you would use
+ * amount should be created with something like {@link Coin#valueOf(int, int)}. Typically you would use
* {@link Transaction#addOutput(Coin, ECKey)} instead of creating an output directly.
*/
public TransactionOutput(NetworkParameters params, @Nullable Transaction parent, Coin value, ECKey to) {
@@ -384,10 +384,42 @@ public class TransactionOutput extends ChildMessage implements Serializable {
}
/**
- * Returns the transaction that owns this output, or throws NullPointerException if unowned.
+ * Returns the transaction that owns this output.
*/
+ @Nullable
public Transaction getParentTransaction() {
- return checkNotNull((Transaction) parent, "Free-standing TransactionOutput");
+ if(parent != null) {
+ return (Transaction) parent;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the transaction hash that owns this output.
+ */
+ @Nullable
+ public Sha256Hash getParentTransactionHash() {
+ if (getParentTransaction() != null) {
+ return getParentTransaction().getHash();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the depth in blocks of the parent tx.
+ *
+ * If the transaction appears in the top block, the depth is one. If it's anything else (pending, dead, unknown)
+ * then -1.
+ * @return The tx depth or -1.
+ */
+ public int getParentTransactionDepthInBlocks() {
+ if (getParentTransaction() != null) {
+ TransactionConfidence confidence = getParentTransaction().getConfidence();
+ if (confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
+ return confidence.getDepthInBlocks();
+ }
+ }
+ return -1;
}
/**
diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutputChanges.java b/core/src/main/java/org/bitcoinj/core/TransactionOutputChanges.java
index 30ad7779..5de25848 100644
--- a/core/src/main/java/org/bitcoinj/core/TransactionOutputChanges.java
+++ b/core/src/main/java/org/bitcoinj/core/TransactionOutputChanges.java
@@ -29,10 +29,10 @@ import java.util.List;
* BIP30 (no duplicate txid creation if the previous one was not fully spent prior to this block) verification.
*/
public class TransactionOutputChanges {
- public final List txOutsCreated;
- public final List txOutsSpent;
+ public final List txOutsCreated;
+ public final List txOutsSpent;
- public TransactionOutputChanges(List txOutsCreated, List txOutsSpent) {
+ public TransactionOutputChanges(List txOutsCreated, List txOutsSpent) {
this.txOutsCreated = txOutsCreated;
this.txOutsSpent = txOutsSpent;
}
@@ -42,17 +42,17 @@ public class TransactionOutputChanges {
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
- txOutsCreated = new LinkedList();
+ txOutsCreated = new LinkedList();
for (int i = 0; i < numOutsCreated; i++)
- txOutsCreated.add(new StoredTransactionOutput(in));
+ txOutsCreated.add(new UTXO(in));
int numOutsSpent = ((in.read() & 0xFF) << 0) |
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
- txOutsSpent = new LinkedList();
+ txOutsSpent = new LinkedList();
for (int i = 0; i < numOutsSpent; i++)
- txOutsSpent.add(new StoredTransactionOutput(in));
+ txOutsSpent.add(new UTXO(in));
}
public void serializeToStream(OutputStream bos) throws IOException {
@@ -61,7 +61,7 @@ public class TransactionOutputChanges {
bos.write(0xFF & (numOutsCreated >> 8));
bos.write(0xFF & (numOutsCreated >> 16));
bos.write(0xFF & (numOutsCreated >> 24));
- for (StoredTransactionOutput output : txOutsCreated) {
+ for (UTXO output : txOutsCreated) {
output.serializeToStream(bos);
}
@@ -70,7 +70,7 @@ public class TransactionOutputChanges {
bos.write(0xFF & (numOutsSpent >> 8));
bos.write(0xFF & (numOutsSpent >> 16));
bos.write(0xFF & (numOutsSpent >> 24));
- for (StoredTransactionOutput output : txOutsSpent) {
+ for (UTXO output : txOutsSpent) {
output.serializeToStream(bos);
}
}
diff --git a/core/src/main/java/org/bitcoinj/core/StoredTransactionOutput.java b/core/src/main/java/org/bitcoinj/core/UTXO.java
similarity index 69%
rename from core/src/main/java/org/bitcoinj/core/StoredTransactionOutput.java
rename to core/src/main/java/org/bitcoinj/core/UTXO.java
index 2a440cc0..f8332972 100644
--- a/core/src/main/java/org/bitcoinj/core/StoredTransactionOutput.java
+++ b/core/src/main/java/org/bitcoinj/core/UTXO.java
@@ -16,15 +16,19 @@
package org.bitcoinj.core;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.*;
import java.math.BigInteger;
/**
- * A StoredTransactionOutput message contains the information necessary to check a spending transaction.
+ * A UTXO message contains the information necessary to check a spending transaction.
* It avoids having to store the entire parentTransaction just to get the hash and index.
- * Its only really useful for MemoryFullPrunedBlockStore, and should probably be moved there
+ * Useful when working with free standing outputs.
*/
-public class StoredTransactionOutput implements Serializable {
+public class UTXO implements Serializable {
+ private static final Logger log = LoggerFactory.getLogger(UTXO.class);
private static final long serialVersionUID = -8744924157056340509L;
/**
@@ -42,62 +46,88 @@ public class StoredTransactionOutput implements Serializable {
private int height;
/** If this output is from a coinbase tx */
private boolean coinbase;
+ /** The address of this output */
+ private String address;
+ /** The type of this address */
+ private int addressType;
/**
* Creates a stored transaction output.
- * @param hash The hash of the containing transaction
+ * @param hash The hash of the containing transaction.
* @param index The outpoint.
* @param value The value available.
* @param height The height this output was created in.
* @param coinbase The coinbase flag.
* @param scriptBytes The script bytes.
*/
- public StoredTransactionOutput(Sha256Hash hash, long index, Coin value, int height, boolean coinbase, byte[] scriptBytes) {
+ public UTXO(Sha256Hash hash,
+ long index,
+ Coin value,
+ int height,
+ boolean coinbase,
+ byte[] scriptBytes) {
this.hash = hash;
this.index = index;
this.value = value;
this.height = height;
this.scriptBytes = scriptBytes;
this.coinbase = coinbase;
+ this.address = "";
+ this.addressType = 0;
}
- public StoredTransactionOutput(Sha256Hash hash, TransactionOutput out, int height, boolean coinbase) {
- this.hash = hash;
- this.index = out.getIndex();
- this.value = out.getValue();
- this.height = height;
- this.scriptBytes = out.getScriptBytes();
- this.coinbase = coinbase;
+ /**
+ * Creates a stored transaction output.
+ * @param hash The hash of the containing transaction.
+ * @param index The outpoint.
+ * @param value The value available.
+ * @param height The height this output was created in.
+ * @param coinbase The coinbase flag.
+ * @param scriptBytes The script bytes.
+ * @param address The address.
+ * @param addressType The address type.
+ */
+ public UTXO(Sha256Hash hash,
+ long index,
+ Coin value,
+ int height,
+ boolean coinbase,
+ byte[] scriptBytes,
+ String address,
+ int addressType) {
+ this(hash, index, value, height, coinbase, scriptBytes);
+ this.address = address;
+ this.addressType = addressType;
}
- public StoredTransactionOutput(InputStream in) throws IOException {
+ public UTXO(InputStream in) throws IOException {
byte[] valueBytes = new byte[8];
if (in.read(valueBytes, 0, 8) != 8)
throw new EOFException();
value = Coin.valueOf(Utils.readInt64(valueBytes, 0));
-
+
int scriptBytesLength = ((in.read() & 0xFF) << 0) |
- ((in.read() & 0xFF) << 8) |
- ((in.read() & 0xFF) << 16) |
- ((in.read() & 0xFF) << 24);
+ ((in.read() & 0xFF) << 8) |
+ ((in.read() & 0xFF) << 16) |
+ ((in.read() & 0xFF) << 24);
scriptBytes = new byte[scriptBytesLength];
if (in.read(scriptBytes) != scriptBytesLength)
throw new EOFException();
-
+
byte[] hashBytes = new byte[32];
if (in.read(hashBytes) != 32)
throw new EOFException();
hash = new Sha256Hash(hashBytes);
-
+
byte[] indexBytes = new byte[4];
if (in.read(indexBytes) != 4)
throw new EOFException();
index = Utils.readUint32(indexBytes, 0);
height = ((in.read() & 0xFF) << 0) |
- ((in.read() & 0xFF) << 8) |
- ((in.read() & 0xFF) << 16) |
- ((in.read() & 0xFF) << 24);
+ ((in.read() & 0xFF) << 8) |
+ ((in.read() & 0xFF) << 16) |
+ ((in.read() & 0xFF) << 24);
byte[] coinbaseByte = new byte[1];
in.read(coinbaseByte);
@@ -156,6 +186,22 @@ public class StoredTransactionOutput implements Serializable {
return coinbase;
}
+ /**
+ * The address of this output.
+ * @return The address.
+ */
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ * The type of the address.
+ * @return The address type.
+ */
+ public int getAddressType() {
+ return addressType;
+ }
+
@Override
public String toString() {
return String.format("Stored TxOut of %s (%s:%d)", value.toFriendlyString(), hash.toString(), index);
@@ -170,23 +216,23 @@ public class StoredTransactionOutput implements Serializable {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- StoredTransactionOutput other = (StoredTransactionOutput) o;
+ UTXO other = (UTXO) o;
return getHash().equals(other.getHash()) &&
- getIndex() == other.getIndex();
+ getIndex() == other.getIndex();
}
public void serializeToStream(OutputStream bos) throws IOException {
Utils.uint64ToByteStreamLE(BigInteger.valueOf(value.value), bos);
-
+
bos.write(0xFF & scriptBytes.length >> 0);
bos.write(0xFF & scriptBytes.length >> 8);
bos.write(0xFF & (scriptBytes.length >> 16));
bos.write(0xFF & (scriptBytes.length >> 24));
bos.write(scriptBytes);
-
+
bos.write(hash.getBytes());
Utils.uint32ToByteStreamLE(index, bos);
-
+
bos.write(0xFF & (height >> 0));
bos.write(0xFF & (height >> 8));
bos.write(0xFF & (height >> 16));
@@ -200,4 +246,4 @@ public class StoredTransactionOutput implements Serializable {
}
bos.write(coinbaseByte);
}
-}
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/bitcoinj/core/UTXOProvider.java b/core/src/main/java/org/bitcoinj/core/UTXOProvider.java
new file mode 100644
index 00000000..abec341b
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/UTXOProvider.java
@@ -0,0 +1,49 @@
+/**
+ * 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 org.bitcoinj.core;
+
+import java.util.List;
+
+/**
+ * A UTXOProvider encapsulates functionality for returning unspent transaction outputs,
+ * for use by the wallet or other code that crafts spends.
+ *
+ * A {@link org.bitcoinj.store.FullPrunedBlockStore} is an internal implementation within bitcoinj.
+ */
+public interface UTXOProvider {
+
+ /**
+ * // TODO currently the access to outputs is by address. Change to ECKey
+ * Get the list of {@link UTXO}'s for a given address.
+ * @param addresses List of address.
+ * @return The list of transaction outputs.
+ * @throws UTXOProvider If there is an error.
+ */
+ List getOpenTransactionOutputs(List addresses) throws UTXOProviderException;
+
+ /**
+ * Get the height of the chain head.
+ * @return The chain head height.
+ * @throws UTXOProvider If there is an error.
+ */
+ int getChainHeadHeight() throws UTXOProviderException;
+
+ /**
+ * The {@link NetworkParameters} of this provider.
+ * @return The network parameters.
+ */
+ NetworkParameters getParams();
+}
diff --git a/core/src/main/java/org/bitcoinj/core/UTXOProviderException.java b/core/src/main/java/org/bitcoinj/core/UTXOProviderException.java
new file mode 100644
index 00000000..71f97fbb
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/UTXOProviderException.java
@@ -0,0 +1,34 @@
+/**
+ * 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 org.bitcoinj.core;
+
+public class UTXOProviderException extends Exception {
+ public UTXOProviderException() {
+ super();
+ }
+
+ public UTXOProviderException(String message) {
+ super(message);
+ }
+
+ public UTXOProviderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UTXOProviderException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/core/Wallet.java b/core/src/main/java/org/bitcoinj/core/Wallet.java
index 6c34019c..76b5e7bf 100644
--- a/core/src/main/java/org/bitcoinj/core/Wallet.java
+++ b/core/src/main/java/org/bitcoinj/core/Wallet.java
@@ -38,6 +38,7 @@ import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.signers.LocalTransactionSigner;
import org.bitcoinj.signers.MissingSigResolutionSigner;
import org.bitcoinj.signers.TransactionSigner;
+import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.store.UnreadableWalletException;
import org.bitcoinj.store.WalletProtobufSerializer;
import org.bitcoinj.utils.BaseTaggableObject;
@@ -58,7 +59,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.google.common.base.Preconditions.*;
@@ -209,6 +209,9 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// Objects that perform transaction signing. Applied subsequently one after another
@GuardedBy("lock") private List signers;
+ // If this is set then the wallet selects spendable candidate outputs from a UTXO provider.
+ @Nullable volatile private UTXOProvider vUTXOProvider;
+
/**
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
* see loadFromFile.
@@ -3600,15 +3603,20 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
public LinkedList calculateAllSpendCandidates(boolean excludeImmatureCoinbases) {
lock.lock();
try {
- LinkedList candidates = Lists.newLinkedList();
- for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
- // Do not try and spend coinbases that were mined too recently, the protocol forbids it.
- if (excludeImmatureCoinbases && !tx.isMature()) continue;
- for (TransactionOutput output : tx.getOutputs()) {
- if (!output.isAvailableForSpending()) continue;
- if (!output.isMine(this)) continue;
- candidates.add(output);
+ LinkedList candidates;
+ if (vUTXOProvider == null) {
+ candidates = Lists.newLinkedList();
+ for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
+ // Do not try and spend coinbases that were mined too recently, the protocol forbids it.
+ if (excludeImmatureCoinbases && !tx.isMature()) continue;
+ for (TransactionOutput output : tx.getOutputs()) {
+ if (!output.isAvailableForSpending()) continue;
+ if (!output.isMine(this)) continue;
+ candidates.add(output);
+ }
}
+ } else {
+ candidates = calculateAllSpendCandidatesFromUTXOProvider(excludeImmatureCoinbases);
}
return candidates;
} finally {
@@ -3616,6 +3624,63 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
+ /**
+ * Returns the spendable candidates from the {@link UTXOProvider} based on keys that the wallet contains.
+ * @return The list of candidates.
+ */
+ protected LinkedList calculateAllSpendCandidatesFromUTXOProvider(boolean excludeImmatureCoinbases){
+ checkState(lock.isHeldByCurrentThread());
+ LinkedList candidates = Lists.newLinkedList();
+ try {
+ int chainHeight = vUTXOProvider.getChainHeadHeight();
+ for (UTXO output : getStoredOutputsFromUTXOProvider()) {
+ boolean coinbase = output.isCoinbase();
+ int depth = chainHeight - output.getHeight() + 1; // the current depth of the output (1 = same as head).
+ // Do not try and spend coinbases that were mined too recently, the protocol forbids it.
+ if (!excludeImmatureCoinbases || !coinbase || depth >= params.getSpendableCoinbaseDepth()) {
+ candidates.add(new FreeStandingTransactionOutput(params, output, chainHeight));
+ }
+ }
+ } catch (UTXOProviderException e) {
+ throw new RuntimeException("UTXO provider error", e);
+ }
+ // We need to handle the pending transactions that we know about.
+ for (Transaction tx : Iterables.concat(pending.values())) {
+ // Remove the spent outputs.
+ for(TransactionInput input : tx.getInputs()) {
+ if (input.getConnectedOutput().isMine(this)) {
+ candidates.remove(input.getConnectedOutput());
+ }
+ }
+ // Add change outputs. Do not try and spend coinbases that were mined too recently, the protocol forbids it.
+ if (!excludeImmatureCoinbases || tx.isMature()) {
+ for (TransactionOutput output : tx.getOutputs()) {
+ if (output.isAvailableForSpending() && output.isMine(this)) {
+ candidates.add(output);
+ }
+ }
+ }
+ }
+ return candidates;
+ }
+
+ /**
+ * Get all the {@link UTXO}'s from the {@link UTXOProvider} based on keys that the
+ * wallet contains.
+ * @return The list of stored outputs.
+ */
+ protected List getStoredOutputsFromUTXOProvider() throws UTXOProviderException {
+ List candidates = new ArrayList();
+ List keys = getActiveKeychain().getLeafKeys();
+ List addresses = new ArrayList();
+ for (ECKey key : keys) {
+ Address address = new Address(params, key.getPubKeyHash());
+ addresses.add(address);
+ }
+ candidates.addAll(vUTXOProvider.getOpenTransactionOutputs(addresses));
+ return candidates;
+ }
+
/** Returns the {@link CoinSelector} object which controls which outputs can be spent by this wallet. */
public CoinSelector getCoinSelector() {
lock.lock();
@@ -3650,8 +3715,97 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
setCoinSelector(AllowUnconfirmedCoinSelector.get());
}
+ /**
+ * Get the {@link UTXOProvider}.
+ * @return The UTXO provider.
+ */
+ @Nullable public UTXOProvider getUTXOProvider() {
+ lock.lock();
+ try {
+ return vUTXOProvider;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Set the {@link UTXOProvider}.
+ *
+ * The wallet will query the provider for the spendable candidates.
+ * The spendable candidates are the outputs controlled exclusively
+ * by private keys contained in the wallet.
+ *
+ * Note that the associated provider must be reattached after a wallet is loaded from disk.
+ * The association is not serialized.
+ *
+ * @param vUTXOProvider The UTXO provider.
+ */
+ public void setUTXOProvider(@Nullable FullPrunedBlockStore vUTXOProvider) {
+ lock.lock();
+ try {
+ checkArgument(vUTXOProvider == null ? true : vUTXOProvider.getParams().equals(params));
+ this.vUTXOProvider = vUTXOProvider;
+ } finally {
+ lock.unlock();
+ }
+ }
+
//endregion
+ /******************************************************************************************************************/
+
+ /**
+ * A custom {@link TransactionOutput} that is free standing. This contains all the information
+ * required for spending without actually having all the linked data (i.e parent tx).
+ *
+ */
+ private class FreeStandingTransactionOutput extends TransactionOutput {
+ private UTXO output;
+ private int chainHeight;
+
+ /**
+ * Construct a free standing Transaction Output.
+ * @param params The network parameters.
+ * @param output The stored output (free standing).
+ */
+ public FreeStandingTransactionOutput(NetworkParameters params, UTXO output, int chainHeight) {
+ super(params, null, output.getValue(), output.getScriptBytes());
+ this.output = output;
+ this.chainHeight = chainHeight;
+ }
+
+ /**
+ * Get the {@link UTXO}.
+ * @return The stored output.
+ */
+ public UTXO getUTXO() {
+ return output;
+ }
+
+ /**
+ * Get the depth withing the chain of the parent tx, depth is 1 if it the output height is the height of
+ * the latest block.
+ * @return The depth.
+ */
+ @Override
+ public int getParentTransactionDepthInBlocks() {
+ return chainHeight - output.getHeight() + 1;
+ }
+
+ @Override
+ public int getIndex() {
+ return (int) output.getIndex();
+ }
+
+ @Override
+ public Sha256Hash getParentTransactionHash() {
+ return output.getHash();
+ }
+ }
+
+ /******************************************************************************************************************/
+
+
/******************************************************************************************************************/
private static class TxOffsetPair implements Comparable {
diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java
index 5d05dd44..d5c396f3 100644
--- a/core/src/main/java/org/bitcoinj/script/Script.java
+++ b/core/src/main/java/org/bitcoinj/script/Script.java
@@ -51,6 +51,15 @@ import static com.google.common.base.Preconditions.*;
*/
public class Script {
+ /** Enumeration to encapsulate the type of this script. */
+ public enum ScriptType {
+ // Do NOT change the ordering of the following definitions because their ordinals are stored in databases.
+ NO_TYPE,
+ P2PKH,
+ PUB_KEY,
+ P2SH
+ };
+
/** Flags to pass to {@link Script#correctlySpends(Transaction, long, Script, Set)}. */
public enum VerifyFlag {
P2SH, // Enable BIP16-style subscript evaluation.
@@ -1458,6 +1467,22 @@ public class Script {
return getProgram();
}
+ /**
+ * Get the {@link org.bitcoinj.script.Script.ScriptType}.
+ * @return The script type.
+ */
+ public ScriptType getScriptType() {
+ ScriptType type = ScriptType.NO_TYPE;
+ if (isSentToAddress()) {
+ type = ScriptType.P2PKH;
+ } else if (isSentToRawPubKey()) {
+ type = ScriptType.PUB_KEY;
+ } else if (isPayToScriptHash()) {
+ type = ScriptType.P2SH;
+ }
+ return type;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/core/src/main/java/org/bitcoinj/store/BlockStore.java b/core/src/main/java/org/bitcoinj/store/BlockStore.java
index bae6090f..f1fc5147 100644
--- a/core/src/main/java/org/bitcoinj/store/BlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/BlockStore.java
@@ -16,6 +16,7 @@
package org.bitcoinj.store;
+import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
@@ -58,4 +59,10 @@ public interface BlockStore {
/** Closes the store. */
void close() throws BlockStoreException;
+
+ /**
+ * Get the {@link org.bitcoinj.core.NetworkParameters} of this store.
+ * @return The network params.
+ */
+ NetworkParameters getParams();
}
diff --git a/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
index 621f15eb..4a3aed8f 100644
--- a/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
@@ -19,7 +19,6 @@ package org.bitcoinj.store;
import com.google.common.collect.Lists;
import org.bitcoinj.core.*;
-import org.bitcoinj.script.Script;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -112,7 +111,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
private static final String UPDATE_UNDOABLEBLOCKS_SQL = "UPDATE undoableBlocks SET txOutChanges=?, transactions=? WHERE hash = ?";
private static final String DELETE_UNDOABLEBLOCKS_SQL = "DELETE FROM undoableBlocks WHERE height <= ?";
- private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase FROM openOutputs WHERE hash = ? AND index = ?";
+ private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase, toaddress, addresstargetable FROM openOutputs WHERE hash = ? AND index = ?";
private static final String SELECT_OPENOUTPUTS_COUNT_SQL = "SELECT COUNT(*) FROM openOutputs WHERE hash = ?";
private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openOutputs (hash, index, height, value, scriptBytes, toAddress, addressTargetable, coinbase) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
private static final String DELETE_OPENOUTPUTS_SQL = "DELETE FROM openOutputs WHERE hash = ? AND index = ?";
@@ -123,8 +122,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
private static final String SELECT_DUMP_UNDOABLEBLOCKS_SQL = "SELECT txOutChanges, transactions FROM undoableBlocks";
private static final String SELECT_DUMP_OPENOUTPUTS_SQL = "SELECT value, scriptBytes FROM openOutputs";
- private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT value, scriptBytes, height FROM openOutputs where toaddress = ?";
- private static final String SELECT_TRANSACTION_OUTPUTS_WITH_HEIGHT_SQL = "SELECT value, scriptBytes, height FROM openOutputs where toaddress = ? AND height <= ?";
+ private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT hash, value, scriptBytes, height, index, coinbase, toaddress, addresstargetable FROM openOutputs where toaddress = ?";
// Select the balance of an address SQL.
private static final String SELECT_BALANCE_SQL = "select sum(value) from openoutputs where toaddress = ?";
@@ -259,14 +257,6 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
return SELECT_TRANSACTION_OUTPUTS_SQL;
}
- /**
- * Get the SQL to select the transaction outputs for a given address and height seen.
- * @return The SQL prepared statement.
- */
- protected String getTrasactionOutputWithHeightSelectSQL() {
- return SELECT_TRANSACTION_OUTPUTS_WITH_HEIGHT_SQL;
- }
-
/**
* Get the SQL to drop all the tables (DDL).
* @return The SQL drop statements.
@@ -921,7 +911,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
}
@Override
- public StoredTransactionOutput getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException {
+ public UTXO getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException {
maybeConnect();
PreparedStatement s = null;
try {
@@ -939,7 +929,16 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
Coin value = Coin.valueOf(results.getLong(2));
byte[] scriptBytes = results.getBytes(3);
boolean coinbase = results.getBoolean(4);
- StoredTransactionOutput txout = new StoredTransactionOutput(hash, index, value, height, coinbase, scriptBytes);
+ String address = results.getString(5);
+ int addressType = results.getInt(6);
+ UTXO txout = new UTXO(hash,
+ index,
+ value,
+ height,
+ coinbase,
+ scriptBytes,
+ address,
+ addressType);
return txout;
} catch (SQLException ex) {
throw new BlockStoreException(ex);
@@ -955,42 +954,9 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
}
@Override
- public void addUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
+ public void addUnspentTransactionOutput(UTXO out) throws BlockStoreException {
maybeConnect();
PreparedStatement s = null;
-
- // Calculate the toAddress (if any)
- String dbAddress = "";
- int type = 0;
- Script outputScript = null;
- try {
- outputScript = new Script(out.getScriptBytes());
- }
- catch (ScriptException e) {
- // Unparseable, but this isn't an error - it's an output not containing an address
- log.info("Could not parse script for output: " + out.getHash().toString());
- }
- if (outputScript != null && (outputScript.isSentToAddress()
- || outputScript.isSentToRawPubKey()
- || outputScript.isPayToScriptHash())) {
- if (outputScript.isSentToAddress()) {
- Address targetAddr = new Address(params, outputScript.getPubKeyHash());
- dbAddress = targetAddr.toString();
- type = 1;
- }
- else if (outputScript.isSentToRawPubKey()) {
- /*
- * Note we use the deprecated getFromAddress here. Coinbase outputs seem to have the target address
- * in the pubkey of the script - perhaps we can rename this function?
- */
- dbAddress = outputScript.getFromAddress(params).toString();
- type = 2;
- } else {
- dbAddress = Address.fromP2SHHash(params, outputScript.getPubKeyHash()).toString();
- type = 3;
- }
- }
-
try {
s = conn.get().prepareStatement(getInsertOpenoutputsSQL());
s.setBytes(1, out.getHash().getBytes());
@@ -999,8 +965,8 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
s.setInt(3, out.getHeight());
s.setLong(4, out.getValue().value);
s.setBytes(5, out.getScriptBytes());
- s.setString(6, dbAddress);
- s.setInt(7, type);
+ s.setString(6, out.getAddress());
+ s.setInt(7, out.getAddressType());
s.setBoolean(8, out.isCoinbase());
s.executeUpdate();
s.close();
@@ -1019,11 +985,11 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
}
@Override
- public void removeUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
+ public void removeUnspentTransactionOutput(UTXO out) throws BlockStoreException {
maybeConnect();
// TODO: This should only need one query (maybe a stored procedure)
if (getTransactionOutput(out.getHash(), out.getIndex()) == null)
- throw new BlockStoreException("Tried to remove a StoredTransactionOutput from DatabaseFullPrunedBlockStore that it didn't have!");
+ throw new BlockStoreException("Tried to remove a UTXO from DatabaseFullPrunedBlockStore that it didn't have!");
try {
PreparedStatement s = conn.get()
.prepareStatement(getDeleteOpenoutputsSQL());
@@ -1105,6 +1071,20 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
}
}
+ @Override
+ public NetworkParameters getParams() {
+ return params;
+ }
+
+ @Override
+ public int getChainHeadHeight() throws UTXOProviderException {
+ try {
+ return getVerifiedChainHead().getHeight();
+ } catch (BlockStoreException e) {
+ throw new UTXOProviderException(e);
+ }
+ }
+
/**
* Resets the store by deleting the contents of the tables and reinitialising them.
* @throws BlockStoreException If the tables couldn't be cleared and initialised.
@@ -1147,12 +1127,11 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
* the all the openoutputs as stored in the DB (binary), then use calculateClientSide=true
*
* @param address The address to calculate the balance of
- * @param calculateClientSide Flag to indicate if the DB returns the raw value/s or the actual balance (summed)
* @return The balance of the address supplied. If the address has not been seen, or there are no outputs open for this
* address, the return value is 0.
* @throws BlockStoreException If there is an error getting the balance.
*/
- protected BigInteger calculateBalanceForAddress(Address address, boolean calculateClientSide) throws BlockStoreException {
+ protected BigInteger calculateBalanceForAddress(Address address) throws BlockStoreException {
maybeConnect();
PreparedStatement s = null;
try {
@@ -1161,14 +1140,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
ResultSet rs = s.executeQuery();
BigInteger balance = BigInteger.ZERO;
while (rs.next()) {
- if(calculateClientSide) {
- // The binary data is returned and we calculate the balance. This could be because the DB
- // doesn't offer a convert function.
- //balance = balance.add(BigInteger.valueOf(rs.getLong(1)));
- balance = balance.add(new BigInteger(rs.getBytes(1)));
- } else {
- return BigInteger.valueOf(rs.getLong(1));
- }
+ return BigInteger.valueOf(rs.getLong(1));
}
return balance;
} catch (SQLException ex) {
@@ -1184,100 +1156,48 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
}
}
- /**
- * Calculate the balance for a coinbase, to-address, or p2sh address.
- *
- * The balance {@link org.bitcoinj.store.DatabaseFullPrunedBlockStore#getBalanceSelectSQL()} returns
- * the balance (summed) as an number.
- *
- * @param address The address to calculate the balance of
- * @return The balance of the address supplied. If the address has not been seen, or there are no outputs open for this
- * address, the return value is 0.
- * @throws BlockStoreException If there is an error getting the balance.
- */
- public BigInteger calculateBalanceForAddress(Address address) throws BlockStoreException {
- return calculateBalanceForAddress(address, false);
- }
-
- /**
- * Get the list of {@link org.bitcoinj.core.TransactionOutput}'s for a given address.
- * @param address The address.
- * @return The list of transaction outputs.
- * @throws BlockStoreException If there is an error getting the list of outputs.
- */
- public List getOpenTransactionOutputs(Address address) throws BlockStoreException {
- return getOpenTransactionOutputs(address, null);
- }
-
- /**
- * Get the list of {@link org.bitcoinj.core.TransactionOutput}'s for a given address and a specified height.
- * @param address The address.
- * @param maxHeight The maximum block height of this tx output (inclusive).
- * @return The list of transaction outputs.
- * @throws BlockStoreException If there is an error getting the list of outputs.
- */
- public List getOpenTransactionOutputs(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
- List outputs = new ArrayList();
- for(List outputsSubList : getOpenTransactionOutputsHeightMap(address, maxHeight).values()) {
- outputs.addAll(outputsSubList);
- }
- return outputs;
- }
-
- /**
- * Get the map of the {@link org.bitcoinj.core.TransactionOutput}'s keyed on their height for a given address.
- * @param address The address.
- * @return The map of transaction outputs (list) keyed by height
- * @throws BlockStoreException If there is an error getting the list out outputs.
- */
- public Map> getOpenTransactionOutputsHeightMap(Address address) throws BlockStoreException {
- return getOpenTransactionOutputsHeightMap(address, null);
- }
-
- /**
- * Get the map of the {@link org.bitcoinj.core.TransactionOutput}'s keyed on their height for a given address
- * and a specified height.
- * @param address The address.
- * @param maxHeight The minimum block height of this tx output (inclusive).
- * @return The map of transaction outputs (list) keyed by height
- * @throws BlockStoreException If there is an error getting the list out outputs.
- */
- public Map> getOpenTransactionOutputsHeightMap(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
- maybeConnect();
+ @Override
+ public List getOpenTransactionOutputs(List addresses) throws UTXOProviderException {
PreparedStatement s = null;
- HashMap> outputsMap = new HashMap>();
+ List outputs = new ArrayList();
try {
- if(maxHeight != null) {
- s = conn.get().prepareStatement(getTrasactionOutputWithHeightSelectSQL());
- s.setInt(2, maxHeight);
- } else {
- s = conn.get().prepareStatement(getTrasactionOutputSelectSQL());
- }
- s.setString(1, address.toString());
- ResultSet rs = s.executeQuery();
- while (rs.next()) {
- Coin amount = Coin.valueOf(rs.getLong(1));
- byte[] scriptBytes = rs.getBytes(2);
- int height = rs.getInt(3);
- TransactionOutput output = new TransactionOutput(params, null, amount, scriptBytes);
- if (outputsMap.containsKey(height)) {
- outputsMap.get(height).add(output);
- } else {
- List outputs = new ArrayList();
+ maybeConnect();
+ s = conn.get().prepareStatement(getTrasactionOutputSelectSQL());
+ for (Address address : addresses) {
+ s.setString(1, address.toString());
+ ResultSet rs = s.executeQuery();
+ while (rs.next()) {
+ Sha256Hash hash = new Sha256Hash(rs.getBytes(1));
+ Coin amount = Coin.valueOf(rs.getLong(2));
+ byte[] scriptBytes = rs.getBytes(3);
+ int height = rs.getInt(4);
+ int index = rs.getInt(5);
+ boolean coinbase = rs.getBoolean(6);
+ String toAddress = rs.getString(7);
+ int addressType = rs.getInt(8);
+ UTXO output = new UTXO(hash,
+ index,
+ amount,
+ height,
+ coinbase,
+ scriptBytes,
+ toAddress,
+ addressType);
outputs.add(output);
- outputsMap.put(height, outputs);
}
}
- return outputsMap;
+ return outputs;
} catch (SQLException ex) {
- throw new BlockStoreException(ex);
+ throw new UTXOProviderException(ex);
+ } catch (BlockStoreException bse) {
+ throw new UTXOProviderException(bse);
} finally {
if (s != null)
try {
s.close();
} catch (SQLException e) {
- throw new BlockStoreException("Could not close statement");
- }
+ throw new UTXOProviderException("Could not close statement", e);
+ }
}
}
diff --git a/core/src/main/java/org/bitcoinj/store/FullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/FullPrunedBlockStore.java
index 5a58ad48..d9b39a1d 100644
--- a/core/src/main/java/org/bitcoinj/store/FullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/FullPrunedBlockStore.java
@@ -16,10 +16,8 @@
package org.bitcoinj.store;
-import org.bitcoinj.core.Sha256Hash;
-import org.bitcoinj.core.StoredBlock;
-import org.bitcoinj.core.StoredTransactionOutput;
-import org.bitcoinj.core.StoredUndoableBlock;
+import org.bitcoinj.core.*;
+
/**
* An implementor of FullPrunedBlockStore saves StoredBlock objects to some storage mechanism.
@@ -43,12 +41,12 @@ import org.bitcoinj.core.StoredUndoableBlock;
* A FullPrunedBlockStore contains a map of hashes to [Full]StoredBlock. The hash is the double digest of the
* Bitcoin serialization of the block header, not the header with the extra data as well.
*
- * A FullPrunedBlockStore also contains a map of hash+index to StoredTransactionOutput. Again, the hash is
+ *
A FullPrunedBlockStore also contains a map of hash+index to UTXO. Again, the hash is
* a standard Bitcoin double-SHA256 hash of the transaction.
*
* FullPrunedBlockStores are thread safe.
*/
-public interface FullPrunedBlockStore extends BlockStore {
+public interface FullPrunedBlockStore extends BlockStore, UTXOProvider {
/**
* Saves the given {@link StoredUndoableBlock} and {@link StoredBlock}. Calculates keys from the {@link StoredBlock}
*
@@ -74,21 +72,21 @@ public interface FullPrunedBlockStore extends BlockStore {
StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException;
/**
- * Gets a {@link StoredTransactionOutput} with the given hash and index, or null if none is found
+ * Gets a {@link org.bitcoinj.core.UTXO} with the given hash and index, or null if none is found
*/
- StoredTransactionOutput getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException;
+ UTXO getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException;
/**
- * Adds a {@link StoredTransactionOutput} to the list of unspent TransactionOutputs
+ * Adds a {@link org.bitcoinj.core.UTXO} to the list of unspent TransactionOutputs
*/
- void addUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException;
+ void addUnspentTransactionOutput(UTXO out) throws BlockStoreException;
/**
- * Removes a {@link StoredTransactionOutput} from the list of unspent TransactionOutputs
+ * Removes a {@link org.bitcoinj.core.UTXO} from the list of unspent TransactionOutputs
* Note that the coinbase of the genesis block should NEVER be spendable and thus never in the list.
* @throws BlockStoreException if there is an underlying storage issue, or out was not in the list.
*/
- void removeUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException;
+ void removeUnspentTransactionOutput(UTXO out) throws BlockStoreException;
/**
* True if this store has any unspent outputs from a transaction with a hash equal to the first parameter
@@ -109,7 +107,7 @@ public interface FullPrunedBlockStore extends BlockStore {
* before a call to commitDatabaseBatchWrite.
*
* If chainHead has a greater height than the non-verified chain head (ie that set with
- * {@link BlockStore.setChainHead}) the non-verified chain head should be set to the one set here.
+ * {@link BlockStore#setChainHead}) the non-verified chain head should be set to the one set here.
* In this way a class using a FullPrunedBlockStore only in full-verification mode can ignore the regular
* {@link BlockStore} functions implemented as a part of a FullPrunedBlockStore.
*/
diff --git a/core/src/main/java/org/bitcoinj/store/MemoryBlockStore.java b/core/src/main/java/org/bitcoinj/store/MemoryBlockStore.java
index cf3d891c..0ea4f4d1 100644
--- a/core/src/main/java/org/bitcoinj/store/MemoryBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/MemoryBlockStore.java
@@ -32,6 +32,7 @@ public class MemoryBlockStore implements BlockStore {
}
};
private StoredBlock chainHead;
+ private NetworkParameters params;
public MemoryBlockStore(NetworkParameters params) {
// Insert the genesis block.
@@ -40,6 +41,7 @@ public class MemoryBlockStore implements BlockStore {
StoredBlock storedGenesis = new StoredBlock(genesisHeader, genesisHeader.getWork(), 0);
put(storedGenesis);
setChainHead(storedGenesis);
+ this.params = params;
} catch (BlockStoreException e) {
throw new RuntimeException(e); // Cannot happen.
} catch (VerificationException e) {
@@ -76,4 +78,9 @@ public class MemoryBlockStore implements BlockStore {
public void close() {
blockMap = null;
}
+
+ @Override
+ public NetworkParameters getParams() {
+ return params;
+ }
}
diff --git a/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
index f86a13d2..332cb428 100644
--- a/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
@@ -42,7 +42,7 @@ class StoredTransactionOutPoint implements Serializable {
this.index = index;
}
- StoredTransactionOutPoint(StoredTransactionOutput out) {
+ StoredTransactionOutPoint(UTXO out) {
this.hash = out.getHash();
this.index = out.getIndex();
}
@@ -132,6 +132,14 @@ class TransactionalHashMap {
}
return map.get(key);
}
+
+ public List values() {
+ List valueTypes = new ArrayList();
+ for (KeyType keyType : map.keySet()) {
+ valueTypes.add(get(keyType));
+ }
+ return valueTypes;
+ }
public void put(KeyType key, ValueType value) {
if (Boolean.TRUE.equals(inTransaction.get())) {
@@ -224,10 +232,10 @@ class TransactionalMultiKeyHashMap {
}
/**
- * Keeps {@link StoredBlock}s, {@link StoredUndoableBlock}s and {@link StoredTransactionOutput}s in memory.
+ * Keeps {@link StoredBlock}s, {@link StoredUndoableBlock}s and {@link org.bitcoinj.core.UTXO}s in memory.
* Used primarily for unit testing.
*/
-public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
+public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore, UTXOProvider {
protected static class StoredBlockAndWasUndoableFlag {
public StoredBlock block;
public boolean wasUndoable;
@@ -236,10 +244,11 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
private TransactionalHashMap blockMap;
private TransactionalMultiKeyHashMap fullBlockMap;
//TODO: Use something more suited to remove-heavy use?
- private TransactionalHashMap transactionOutputMap;
+ private TransactionalHashMap transactionOutputMap;
private StoredBlock chainHead;
private StoredBlock verifiedChainHead;
private int fullStoreDepth;
+ private NetworkParameters params;
/**
* Set up the MemoryFullPrunedBlockStore
@@ -249,7 +258,7 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
public MemoryFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth) {
blockMap = new TransactionalHashMap();
fullBlockMap = new TransactionalMultiKeyHashMap();
- transactionOutputMap = new TransactionalHashMap();
+ transactionOutputMap = new TransactionalHashMap();
this.fullStoreDepth = fullStoreDepth > 0 ? fullStoreDepth : 1;
// Insert the genesis block.
try {
@@ -260,6 +269,7 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
setVerifiedChainHead(storedGenesisHeader);
+ this.params = params;
} catch (BlockStoreException e) {
throw new RuntimeException(e); // Cannot happen.
} catch (VerificationException e) {
@@ -343,22 +353,22 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
@Override
@Nullable
- public synchronized StoredTransactionOutput getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException {
+ public synchronized UTXO getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException {
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
return transactionOutputMap.get(new StoredTransactionOutPoint(hash, index));
}
@Override
- public synchronized void addUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
+ public synchronized void addUnspentTransactionOutput(UTXO out) throws BlockStoreException {
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
transactionOutputMap.put(new StoredTransactionOutPoint(out), out);
}
@Override
- public synchronized void removeUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
+ public synchronized void removeUnspentTransactionOutput(UTXO out) throws BlockStoreException {
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
if (transactionOutputMap.remove(new StoredTransactionOutPoint(out)) == null)
- throw new BlockStoreException("Tried to remove a StoredTransactionOutput from MemoryFullPrunedBlockStore that it didn't have!");
+ throw new BlockStoreException("Tried to remove a UTXO from MemoryFullPrunedBlockStore that it didn't have!");
}
@Override
@@ -389,4 +399,34 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
return true;
return false;
}
+
+ @Override
+ public NetworkParameters getParams() {
+ return params;
+ }
+
+ @Override
+ public int getChainHeadHeight() throws UTXOProviderException {
+ try {
+ return getVerifiedChainHead().getHeight();
+ } catch (BlockStoreException e) {
+ throw new UTXOProviderException(e);
+ }
+ }
+
+ @Override
+ public List getOpenTransactionOutputs(List addresses) throws UTXOProviderException {
+ // This is *NOT* optimal: We go through all the outputs and select the ones we are looking for.
+ // If someone uses this store for production then they have a lot more to worry about than an inefficient impl :)
+ List foundOutputs = new ArrayList();
+ List outputsList = transactionOutputMap.values();
+ for (UTXO output : outputsList) {
+ for (Address address : addresses) {
+ if (output.getAddress().equals(address.toString())) {
+ foundOutputs.add(output);
+ }
+ }
+ }
+ return foundOutputs;
+ }
}
diff --git a/core/src/main/java/org/bitcoinj/store/MySQLFullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/MySQLFullPrunedBlockStore.java
index e240fef7..07d4a7c8 100644
--- a/core/src/main/java/org/bitcoinj/store/MySQLFullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/MySQLFullPrunedBlockStore.java
@@ -55,7 +55,7 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
" hash blob NOT NULL,\n" +
" height integer NOT NULL,\n" +
" txoutchanges mediumblob,\n" +
- " transactions blob,\n" +
+ " transactions mediumblob,\n" +
" CONSTRAINT `undoableblocks_pk` PRIMARY KEY (hash(28)) USING btree\n" +
")\n";
@@ -72,16 +72,18 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
")\n";
// Some indexes to speed up inserts
- private static final String CREATE_OUTPUTS_ADDRESS_MULTI_INDEX = "CREATE INDEX openoutputs_hash_index_height_toaddress_idx ON openoutputs (hash(32), `index`, height, toaddress) USING btree";
- private static final String CREATE_OUTPUTS_TOADDRESS_INDEX = "CREATE INDEX openoutputs_toaddress_idx ON openoutputs (toaddress) USING btree";
- private static final String CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX = "CREATE INDEX openoutputs_addresstargetable_idx ON openoutputs (addresstargetable) USING btree";
- private static final String CREATE_OUTPUTS_HASH_INDEX = "CREATE INDEX openoutputs_hash_idx ON openoutputs (hash(32)) USING btree";
- private static final String CREATE_UNDOABLE_TABLE_INDEX = "CREATE INDEX undoableblocks_height_idx ON undoableBlocks (height) USING btree";
+ private static final String CREATE_OUTPUTS_ADDRESS_MULTI_INDEX = "CREATE INDEX openoutputs_hash_index_height_toaddress_idx ON openoutputs (hash(32), `index`, height, toaddress) USING btree";
+ private static final String CREATE_OUTPUTS_TOADDRESS_INDEX = "CREATE INDEX openoutputs_toaddress_idx ON openoutputs (toaddress) USING btree";
+ private static final String CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX = "CREATE INDEX openoutputs_addresstargetable_idx ON openoutputs (addresstargetable) USING btree";
+ private static final String CREATE_OUTPUTS_HASH_INDEX = "CREATE INDEX openoutputs_hash_idx ON openoutputs (hash(32)) USING btree";
+ private static final String CREATE_UNDOABLE_TABLE_INDEX = "CREATE INDEX undoableblocks_height_idx ON undoableBlocks (height) USING btree";
// SQL involving index column (table openOutputs) overridden as it is a reserved word and must be back ticked in MySQL.
- private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase FROM openOutputs WHERE hash = ? AND `index` = ?";
- private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openOutputs (hash, `index`, height, value, scriptBytes, toAddress, addressTargetable, coinbase) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
- private static final String DELETE_OPENOUTPUTS_SQL = "DELETE FROM openOutputs WHERE hash = ? AND `index` = ?";
+ private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase, toaddress, addresstargetable FROM openOutputs WHERE hash = ? AND `index` = ?";
+ private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openOutputs (hash, `index`, height, value, scriptBytes, toAddress, addressTargetable, coinbase) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+ private static final String DELETE_OPENOUTPUTS_SQL = "DELETE FROM openOutputs WHERE hash = ? AND `index` = ?";
+
+ private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT hash, value, scriptBytes, height, `index`, coinbase, toaddress, addresstargetable FROM openOutputs where toaddress = ?";
/**
* Creates a new MySQLFullPrunedBlockStore.
@@ -119,6 +121,11 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
return DELETE_OPENOUTPUTS_SQL;
}
+ @Override
+ protected String getTrasactionOutputSelectSQL() {
+ return SELECT_TRANSACTION_OUTPUTS_SQL;
+ }
+
@Override
protected List getCreateTablesSQL() {
List sqlStatements = new ArrayList();
diff --git a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java
index 661b07d5..38634aad 100644
--- a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java
@@ -276,6 +276,11 @@ public class SPVBlockStore implements BlockStore {
}
}
+ @Override
+ public NetworkParameters getParams() {
+ return params;
+ }
+
protected static final int RECORD_SIZE = 32 /* hash */ + StoredBlock.COMPACT_SERIALIZED_SIZE;
// File format:
diff --git a/core/src/main/java/org/bitcoinj/wallet/CoinSelection.java b/core/src/main/java/org/bitcoinj/wallet/CoinSelection.java
index ff4fba53..d9e41c4a 100644
--- a/core/src/main/java/org/bitcoinj/wallet/CoinSelection.java
+++ b/core/src/main/java/org/bitcoinj/wallet/CoinSelection.java
@@ -7,7 +7,7 @@ import java.util.Collection;
/**
* Represents the results of a
- * {@link CoinSelector#select(Coin, java.util.LinkedList)} operation. A
+ * {@link CoinSelector#select(Coin, java.util.List)} operation. A
* coin selection represents a list of spendable transaction outputs that sum together to give valueGathered.
* Different coin selections could be produced by different coin selectors from the same input set, according
* to their varying policies.
diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java b/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java
index e5bff5bf..c8a593ca 100644
--- a/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java
+++ b/core/src/main/java/org/bitcoinj/wallet/DefaultCoinSelector.java
@@ -48,14 +48,8 @@ public class DefaultCoinSelector implements CoinSelector {
Collections.sort(outputs, new Comparator() {
@Override
public int compare(TransactionOutput a, TransactionOutput b) {
- int depth1 = 0;
- int depth2 = 0;
- TransactionConfidence conf1 = a.getParentTransaction().getConfidence();
- TransactionConfidence conf2 = b.getParentTransaction().getConfidence();
- if (conf1.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
- depth1 = conf1.getDepthInBlocks();
- if (conf2.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
- depth2 = conf2.getDepthInBlocks();
+ int depth1 = a.getParentTransactionDepthInBlocks();
+ int depth2 = b.getParentTransactionDepthInBlocks();
Coin aValue = a.getValue();
Coin bValue = b.getValue();
BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1));
@@ -66,8 +60,8 @@ public class DefaultCoinSelector implements CoinSelector {
int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
- BigInteger aHash = a.getParentTransaction().getHash().toBigInteger();
- BigInteger bHash = b.getParentTransaction().getHash().toBigInteger();
+ BigInteger aHash = a.getParentTransactionHash().toBigInteger();
+ BigInteger bHash = b.getParentTransactionHash().toBigInteger();
return aHash.compareTo(bHash);
}
});
@@ -75,7 +69,10 @@ public class DefaultCoinSelector implements CoinSelector {
/** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */
protected boolean shouldSelect(Transaction tx) {
- return isSelectable(tx);
+ if (tx != null) {
+ return isSelectable(tx);
+ }
+ return true;
}
public static boolean isSelectable(Transaction tx) {
diff --git a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java
index 7ff39c6f..a88fff39 100644
--- a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java
+++ b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java
@@ -17,6 +17,7 @@
package org.bitcoinj.core;
+import com.google.common.collect.Lists;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
@@ -24,6 +25,7 @@ import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.utils.BlockFileLoader;
import org.bitcoinj.utils.BriefLogFormatter;
+import org.bitcoinj.wallet.WalletTransaction;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
@@ -32,6 +34,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Arrays;
+import java.util.List;
import static org.bitcoinj.core.Coin.FIFTY_COINS;
import static org.junit.Assert.*;
@@ -70,7 +73,6 @@ public abstract class AbstractFullPrunedBlockChainTest
RuleList blockList = generator.getBlocksToTest(false, false, null);
store = createStore(params, blockList.maximumReorgBlockCount);
- resetStore(store);
chain = new FullPrunedBlockChain(params, store);
for (Rule rule : blockList.list) {
@@ -108,12 +110,14 @@ public abstract class AbstractFullPrunedBlockChainTest
fail();
}
}
+ try {
+ store.close();
+ } catch (Exception e) {}
}
@Test
public void skipScripts() throws Exception {
store = createStore(params, 10);
- resetStore(store);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
@@ -144,13 +148,15 @@ public abstract class AbstractFullPrunedBlockChainTest
} 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);
- resetStore(store);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
@@ -168,13 +174,13 @@ public abstract class AbstractFullPrunedBlockChainTest
chain.add(rollingBlock);
}
- WeakReference out = new WeakReference
+ WeakReference out = new WeakReference
(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.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[]{}));
t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
rollingBlock.addTransaction(t);
rollingBlock.solve();
@@ -199,6 +205,9 @@ public abstract class AbstractFullPrunedBlockChainTest
assertNull(undoBlock.get());
assertNull(changes.get());
assertNull(out.get());
+ try {
+ store.close();
+ } catch (Exception e) {}
}
@Test
@@ -212,5 +221,121 @@ public abstract class AbstractFullPrunedBlockChainTest
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 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) {}
}
}
diff --git a/core/src/test/java/org/bitcoinj/core/H2FullPrunedBlockChainTest.java b/core/src/test/java/org/bitcoinj/core/H2FullPrunedBlockChainTest.java
index 96b14d25..2ff7dc4c 100644
--- a/core/src/test/java/org/bitcoinj/core/H2FullPrunedBlockChainTest.java
+++ b/core/src/test/java/org/bitcoinj/core/H2FullPrunedBlockChainTest.java
@@ -25,6 +25,7 @@ public class H2FullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest
private void deleteFiles() {
maybeDelete("test.h2.db");
maybeDelete("test.trace.db");
+ maybeDelete("test.lock.db");
}
private void maybeDelete(String s) {