3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 14:52:16 +00:00

1) Update the full stores to return UTXO transactions.

2) The wallet can now use a UTXOProvider to create spends.
3) Updated MySQL field blob size.  It's capacity isn't sufficient.
4) Updated internal objects to be able to cope with a null parent tx (spend free standing outputs).
This commit is contained in:
Kalpesh Parmar 2014-11-19 13:17:59 +00:00 committed by Mike Hearn
parent 2286d7e167
commit 96a82800fd
21 changed files with 782 additions and 263 deletions

View File

@ -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<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
LinkedList<UTXO> txOutsSpent = new LinkedList<UTXO>();
LinkedList<UTXO> txOutsCreated = new LinkedList<UTXO>();
long sigOps = 0;
final Set<VerifyFlag> 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<Transaction> transactions = block.getTransactions();
if (transactions != null) {
LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
LinkedList<UTXO> txOutsSpent = new LinkedList<UTXO>();
LinkedList<UTXO> txOutsCreated = new LinkedList<UTXO>();
long sigOps = 0;
final Set<VerifyFlag> 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();

View File

@ -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);

View File

@ -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;
}
/**

View File

@ -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.
*
* <p>If the transaction appears in the top block, the depth is one. If it's anything else (pending, dead, unknown)
* then -1.</p>
* @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;
}
/**

View File

@ -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.</p>
*/
public class TransactionOutputChanges {
public final List<StoredTransactionOutput> txOutsCreated;
public final List<StoredTransactionOutput> txOutsSpent;
public final List<UTXO> txOutsCreated;
public final List<UTXO> txOutsSpent;
public TransactionOutputChanges(List<StoredTransactionOutput> txOutsCreated, List<StoredTransactionOutput> txOutsSpent) {
public TransactionOutputChanges(List<UTXO> txOutsCreated, List<UTXO> 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<StoredTransactionOutput>();
txOutsCreated = new LinkedList<UTXO>();
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<StoredTransactionOutput>();
txOutsSpent = new LinkedList<UTXO>();
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);
}
}

View File

@ -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);
}
}
}

View File

@ -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.
*
* <p>A {@link org.bitcoinj.store.FullPrunedBlockStore} is an internal implementation within bitcoinj.</p>
*/
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<UTXO> getOpenTransactionOutputs(List<Address> 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();
}

View File

@ -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);
}
}

View File

@ -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<TransactionSigner> 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<TransactionOutput> calculateAllSpendCandidates(boolean excludeImmatureCoinbases) {
lock.lock();
try {
LinkedList<TransactionOutput> 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<TransactionOutput> 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<TransactionOutput> calculateAllSpendCandidatesFromUTXOProvider(boolean excludeImmatureCoinbases){
checkState(lock.isHeldByCurrentThread());
LinkedList<TransactionOutput> 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<UTXO> getStoredOutputsFromUTXOProvider() throws UTXOProviderException {
List<UTXO> candidates = new ArrayList<UTXO>();
List<DeterministicKey> keys = getActiveKeychain().getLeafKeys();
List<Address> addresses = new ArrayList<Address>();
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}.
*
* <p>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.</p>
*
* <p>Note that the associated provider must be reattached after a wallet is loaded from disk.
* The association is not serialized.</p>
*
* @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<TxOffsetPair> {

View File

@ -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;

View File

@ -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();
}

View File

@ -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</p>
*
* @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.
*
* <p>The balance {@link org.bitcoinj.store.DatabaseFullPrunedBlockStore#getBalanceSelectSQL()} returns
* the balance (summed) as an number.</p>
*
* @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<TransactionOutput> 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<TransactionOutput> getOpenTransactionOutputs(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
List<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
for(List<TransactionOutput> 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<Integer, List<TransactionOutput>> 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<Integer, List<TransactionOutput>> getOpenTransactionOutputsHeightMap(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
maybeConnect();
@Override
public List<UTXO> getOpenTransactionOutputs(List<Address> addresses) throws UTXOProviderException {
PreparedStatement s = null;
HashMap<Integer, List<TransactionOutput>> outputsMap = new HashMap<Integer, List<TransactionOutput>>();
List<UTXO> outputs = new ArrayList<UTXO>();
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<TransactionOutput>();
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);
}
}
}

View File

@ -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.*;
/**
* <p>An implementor of FullPrunedBlockStore saves StoredBlock objects to some storage mechanism.</p>
@ -43,12 +41,12 @@ import org.bitcoinj.core.StoredUndoableBlock;
* <p>A FullPrunedBlockStore contains a map of hashes to [Full]StoredBlock. The hash is the double digest of the
* Bitcoin serialization of the block header, <b>not</b> the header with the extra data as well.</p>
*
* <p>A FullPrunedBlockStore also contains a map of hash+index to StoredTransactionOutput. Again, the hash is
* <p>A FullPrunedBlockStore also contains a map of hash+index to UTXO. Again, the hash is
* a standard Bitcoin double-SHA256 hash of the transaction.</p>
*
* <p>FullPrunedBlockStores are thread safe.</p>
*/
public interface FullPrunedBlockStore extends BlockStore {
public interface FullPrunedBlockStore extends BlockStore, UTXOProvider {
/**
* <p>Saves the given {@link StoredUndoableBlock} and {@link StoredBlock}. Calculates keys from the {@link StoredBlock}</p>
*
@ -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.
*/

View File

@ -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;
}
}

View File

@ -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<KeyType, ValueType> {
}
return map.get(key);
}
public List<ValueType> values() {
List<ValueType> valueTypes = new ArrayList<ValueType>();
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<UniqueKeyType, MultiKeyType, ValueType> {
}
/**
* 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<Sha256Hash, StoredBlockAndWasUndoableFlag> blockMap;
private TransactionalMultiKeyHashMap<Sha256Hash, Integer, StoredUndoableBlock> fullBlockMap;
//TODO: Use something more suited to remove-heavy use?
private TransactionalHashMap<StoredTransactionOutPoint, StoredTransactionOutput> transactionOutputMap;
private TransactionalHashMap<StoredTransactionOutPoint, UTXO> 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<Sha256Hash, StoredBlockAndWasUndoableFlag>();
fullBlockMap = new TransactionalMultiKeyHashMap<Sha256Hash, Integer, StoredUndoableBlock>();
transactionOutputMap = new TransactionalHashMap<StoredTransactionOutPoint, StoredTransactionOutput>();
transactionOutputMap = new TransactionalHashMap<StoredTransactionOutPoint, UTXO>();
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<UTXO> getOpenTransactionOutputs(List<Address> 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<UTXO> foundOutputs = new ArrayList<UTXO>();
List<UTXO> outputsList = transactionOutputMap.values();
for (UTXO output : outputsList) {
for (Address address : addresses) {
if (output.getAddress().equals(address.toString())) {
foundOutputs.add(output);
}
}
}
return foundOutputs;
}
}

View File

@ -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<String> getCreateTablesSQL() {
List<String> sqlStatements = new ArrayList<String>();

View File

@ -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:

View File

@ -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.

View File

@ -48,14 +48,8 @@ public class DefaultCoinSelector implements CoinSelector {
Collections.sort(outputs, new Comparator<TransactionOutput>() {
@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) {

View File

@ -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<StoredTransactionOutput> out = new WeakReference<StoredTransactionOutput>
WeakReference<UTXO> out = new WeakReference<UTXO>
(store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex()));
rollingBlock = rollingBlock.createNextBlock(null);
Transaction t = new Transaction(params);
// Entirely invalid scriptPubKey
t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {}));
t.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<UTXO> outputs = store.getOpenTransactionOutputs(Lists.newArrayList(address));
assertNotNull(outputs);
assertEquals("Wrong Number of Outputs", 1, outputs.size());
UTXO output = outputs.get(0);
assertEquals("The address is not equal", address.toString(), output.getAddress());
assertEquals("The amount is not equal", totalAmount, output.getValue());
outputs = null;
output = null;
try {
store.close();
} catch (Exception e) {}
}
@Test
public void testUTXOProviderWithWallet() throws Exception {
final int UNDOABLE_BLOCKS_STORED = 10;
store = createStore(params, UNDOABLE_BLOCKS_STORED);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output.
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
Transaction transaction = rollingBlock.getTransactions().get(0);
TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash());
byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes();
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
}
rollingBlock = rollingBlock.createNextBlock(null);
// Create 1 BTC spend to a key in this wallet (to ourselves).
Wallet wallet = new Wallet(params);
assertEquals("Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
assertEquals("Estimated balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
wallet.setUTXOProvider(store);
ECKey toKey = wallet.freshReceiveKey();
Coin amount = Coin.valueOf(100000000);
Transaction t = new Transaction(params);
t.addOutput(new TransactionOutput(params, t, amount, toKey));
t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
rollingBlock.addTransaction(t);
rollingBlock.solve();
chain.add(rollingBlock);
// Create another spend of 1/2 the value of BTC we have available using the wallet (store coin selector).
ECKey toKey2 = new ECKey();
Coin amount2 = amount.divide(2);
Address address2 = new Address(params, toKey2.getPubKeyHash());
Wallet.SendRequest req = Wallet.SendRequest.to(address2, amount2);
wallet.completeTx(req);
wallet.commitTx(req.tx);
Coin fee = req.fee;
// There should be one pending tx (our spend).
assertEquals("Wrong number of PENDING.4", 1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
Coin totalPendingTxAmount = Coin.ZERO;
for (Transaction tx : wallet.getPendingTransactions()) {
totalPendingTxAmount = totalPendingTxAmount.add(tx.getValueSentToMe(wallet));
}
// The availbale balance should be the 0 (as we spent the 1 BTC that's pending) and estimated should be 1/2 - fee BTC
assertEquals("Available balance is incorrect", Coin.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
assertEquals("Estimated balance is incorrect", amount2.subtract(fee), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
assertEquals("Pending tx amount is incorrect", amount2.subtract(fee), totalPendingTxAmount);
try {
store.close();
} catch (Exception e) {}
}
}

View File

@ -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) {