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:
parent
2286d7e167
commit
96a82800fd
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
49
core/src/main/java/org/bitcoinj/core/UTXOProvider.java
Normal file
49
core/src/main/java/org/bitcoinj/core/UTXOProvider.java
Normal 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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user