3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

TransactionConfidence changes (coinbase phase 2) + Mike's feedback

This commit is contained in:
Jim Burton 2012-05-21 15:18:49 +01:00 committed by Mike Hearn
parent d1c2dfecbe
commit 80f141cbf5
11 changed files with 487 additions and 111 deletions

View File

@ -100,6 +100,14 @@ message TransactionConfidence {
// multiple transactions in the case of several inputs being re-spent by several transactions but we don't
// bother to track them all, just the first. This only makes sense if type = OVERRIDDEN_BY_DOUBLE_SPEND.
optional bytes overriding_transaction = 3;
// If type == BUILDING then this is the depth of the transaction in the blockchain.
// Zero confirmations: depth = 0, one confirmation: depth = 1 etc.
optional int32 depth = 4;
// If type == BUILDING then this is the cumulative workDone for the block the transaction appears in, together with
// all blocks that bury it.
optional int64 work_done = 5;
}
/** A bitcoin transaction */

View File

@ -290,7 +290,7 @@ public class BlockChain {
// can be updated to take into account the re-organize. We might also have received new coins we didn't have
// before and our previous spends might have been undone.
for (Wallet wallet : wallets) {
wallet.reorganize(oldBlocks, newBlocks);
wallet.reorganize(splitPoint, oldBlocks, newBlocks);
}
// Update the pointer to the best known block.
setChainHead(newChainHead);

View File

@ -16,6 +16,7 @@
package com.google.bitcoin.core;
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -259,13 +260,23 @@ public class Transaction extends ChildMessage implements Serializable {
if (bestChain && (updatedAt == null || updatedAt.getTime() == 0 || updatedAt.getTime() > blockTime)) {
updatedAt = new Date(blockTime);
}
addBlockAppearance(block.getHeader().getHash());
if (bestChain) {
// This can cause event listeners on TransactionConfidence to run. After this line completes, the wallets
// This can cause event listeners on TransactionConfidence to run. After these lines complete, the wallets
// state may have changed!
getConfidence().setAppearedAtChainHeight(block.getHeight());
TransactionConfidence transactionConfidence = getConfidence();
transactionConfidence.setAppearedAtChainHeight(block.getHeight());
// Reset the confidence block depth.
transactionConfidence.setDepthInBlocks(0);
// Reset the work done.
transactionConfidence.setWorkDone(BigInteger.ZERO);
// The transaction is now on the best chain.
transactionConfidence.setConfidenceType(ConfidenceType.BUILDING);
}
}
@ -278,7 +289,10 @@ public class Transaction extends ChildMessage implements Serializable {
/** Called by the wallet once a re-org means we don't appear in the best chain anymore. */
void notifyNotOnBestChain() {
getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_IN_BEST_CHAIN);
TransactionConfidence transactionConfidence = getConfidence();
transactionConfidence.setConfidenceType(TransactionConfidence.ConfidenceType.NOT_IN_BEST_CHAIN);
transactionConfidence.setDepthInBlocks(0);
transactionConfidence.setWorkDone(BigInteger.ZERO);
}
/**

View File

@ -16,7 +16,6 @@
package com.google.bitcoin.core;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.utils.EventListenerInvoker;
import com.google.common.base.Preconditions;
@ -50,11 +49,9 @@ import java.util.Set;
* <p>Alternatively, you may know that the transaction is "dead", that is, one or more of its inputs have
* been double spent and will never confirm unless there is another re-org.</p>
*
* <p>TransactionConfidence is purely a data structure, it doesn't try and keep itself up to date. To have fresh
* confidence data, you need to ensure the owning {@link Transaction} is being updated by something, like
* a {@link Wallet}.</p>Confidence objects <b>are live</b> and can be updated by other threads running in parallel
* to your own. To make a copy that won't be changed, use
* {@link com.google.bitcoin.core.TransactionConfidence#duplicate()}.
* <p>TransactionConfidence is updated via the {@link com.google.bitcoin.core.TransactionConfidence#notifyWorkDone()}
* method to ensure the block depth and work done are up to date.</p>
* To make a copy that won't be changed, use {@link com.google.bitcoin.core.TransactionConfidence#duplicate()}.
*/
public class TransactionConfidence implements Serializable {
private static final long serialVersionUID = 4577920141400556444L;
@ -70,6 +67,18 @@ public class TransactionConfidence implements Serializable {
// Lazily created listeners array.
private transient ArrayList<Listener> listeners;
/**
* The depth of the transaction on the best chain in blocks. An unconfirmed block has depth 0, after one confirmation
* its depth is 1.
*/
private int depth;
/**
* The cumulative work done for the blocks that bury this transaction. BigInteger.ZERO if the transaction is not
* on the best chain.
*/
private BigInteger workDone = BigInteger.ZERO;
// TODO: The advice below is a mess. There should be block chain listeners, see issue 94.
/**
* <p>Adds an event listener that will be run when this confidence object is updated. The listener will be locked and
@ -278,6 +287,18 @@ public class TransactionConfidence implements Serializable {
return builder.toString();
}
/**
* Called by the wallet when the tx appears on the best chain and a new block is added to the top.
* Updates the internal counter that tracks how deeply buried the block is.
* Work is the value of block.getWork().
*/
public synchronized void notifyWorkDone(Block block) throws VerificationException {
if (getConfidenceType() == ConfidenceType.BUILDING) {
this.depth++;
this.workDone = this.workDone.add(block.getWork());
}
}
/**
* Depth in the chain is an approximation of how much time has elapsed since the transaction has been confirmed. On
* average there is supposed to be a new block every 10 minutes, but the actual rate may vary. The reference
@ -289,16 +310,21 @@ public class TransactionConfidence implements Serializable {
* chain yet, throws IllegalStateException, so use {@link com.google.bitcoin.core.TransactionConfidence#getConfidenceType()}
* to check first.
*
* @param chain a {@link BlockChain} instance.
* @throws IllegalStateException if confidence type != BUILDING.
* @return depth
*/
public synchronized int getDepthInBlocks(BlockChain chain) {
public synchronized int getDepthInBlocks() {
if (getConfidenceType() != ConfidenceType.BUILDING) {
throw new IllegalStateException("Confidence type is not BUILDING");
}
int height = getAppearedAtChainHeight();
return chain.getBestChainHeight() - height + 1;
return depth;
}
/*
* Set the depth in blocks. Having one block confirmation is a depth of one.
*/
public synchronized void setDepthInBlocks(int depth) {
this.depth = depth;
}
/**
@ -307,24 +333,18 @@ public class TransactionConfidence implements Serializable {
* blocks per hour by adjusting the difficulty target. So to know how much real computation effort is needed to
* reverse a transaction, counting blocks is not enough.
*
* @param chain
* @throws IllegalStateException if confidence type is not BUILDING
* @return estimated number of hashes needed to reverse the transaction.
*/
public synchronized BigInteger getWorkDone(BlockChain chain) throws BlockStoreException {
int depth;
synchronized (this) {
if (getConfidenceType() != ConfidenceType.BUILDING)
throw new IllegalStateException("Confidence type is " + getConfidenceType() + ", not BUILDING");
depth = getDepthInBlocks(chain);
public synchronized BigInteger getWorkDone() {
if (getConfidenceType() != ConfidenceType.BUILDING) {
throw new IllegalStateException("Confidence type is not BUILDING");
}
BigInteger work = BigInteger.ZERO;
StoredBlock block = chain.getChainHead();
for (; depth > 0; depth--) {
work = work.add(block.getChainWork());
block = block.getPrev(chain.blockStore);
}
return work;
return workDone;
}
public synchronized void setWorkDone(BigInteger workDone) {
this.workDone = workDone;
}
/**

View File

@ -16,6 +16,7 @@
package com.google.bitcoin.core;
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.bitcoin.core.WalletTransaction.Pool;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.EventListenerInvoker;
@ -551,29 +552,39 @@ public class Wallet implements Serializable {
log.info("Balance is now: " + bitcoinValueToFriendlyString(getBalance()));
// Store the block hash
if (bestChain) {
if (block != null && block.getHeader() != null) {
// Check to see if this block has been seen before
Sha256Hash newBlockHash = block.getHeader().getHash();
if (!newBlockHash.equals(getLastBlockSeenHash())) {
// new hash
setLastBlockSeenHash(newBlockHash);
}
}
}
// WARNING: The code beyond this point can trigger event listeners on transaction confidence objects, which are
// in turn allowed to re-enter the Wallet. This means we cannot assume anything about the state of the wallet
// from now on. The balance just received may already be spent.
// Mark the tx as appearing in this block so we can find it later after a re-org. This also lets the
// transaction update its confidence and timestamp bookkeeping data.
// Mark the tx as appearing in this block so we can find it later after a re-org.
if (block != null) {
tx.setBlockAppearance(block, bestChain);
invokeOnTransactionConfidenceChanged(tx);
}
// If this is the first time the the block hash has been seen, store it and notify transactions of the block.
if (bestChain) {
if (block != null && block.getHeader() != null) {
// Check to see if this block has been seen before.
Sha256Hash newBlockHash = block.getHeader().getHash();
if (!newBlockHash.equals(getLastBlockSeenHash())) {
// Store the new block hash.
setLastBlockSeenHash(newBlockHash);
// Notify all the BUILDING transactions of the new block.
// This is so that they can update their work done and depth.
Set<Transaction> transactions = getTransactions(true, false);
for (Transaction t : transactions) {
t.getConfidence().notifyWorkDone(block.getHeader());
}
}
}
}
// Update the transaction confidence and timestamp bookkeeping data.
invokeOnTransactionConfidenceChanged(tx);
// Inform anyone interested that we have received or sent coins but only if:
// - This is not due to a re-org.
// - The coins appeared on the best chain.
@ -598,7 +609,7 @@ public class Wallet implements Serializable {
invokeOnCoinsSent(tx, prevBalance, newBalance);
}
}
checkState(isConsistent());
}
@ -789,7 +800,6 @@ public class Wallet implements Serializable {
/**
* Returns a set of all transactions in the wallet.
*
* @param includeDead If true, transactions that were overridden by a double spend are included.
* @param includeInactive If true, transactions that are on side chains (are unspendable) are included.
*/
@ -1370,7 +1380,7 @@ public class Wallet implements Serializable {
*
* The oldBlocks/newBlocks lists are ordered height-wise from top first to bottom last.
*/
synchronized void reorganize(List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
synchronized void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
// This runs on any peer thread with the block chain synchronized.
//
// The reorganize functionality of the wallet is tested in ChainSplitTests.
@ -1486,9 +1496,32 @@ public class Wallet implements Serializable {
// Now replay the act of receiving the blocks that were previously in a side chain. This will:
// - Move any transactions that were pending and are now accepted into the right bucket.
// - Connect the newly active transactions.
Collections.reverse(newBlocks); // Need bottom-to-top but we get top-to-bottom.
// The old blocks have contributed to the depth and work done for all the transactions in the
// wallet that are in blocks up to and including the chain split block.
// The total depth and work done is calculated here and then subtracted from the appropriate transactions.
int depthToSubtract = oldBlocks == null ? 0 : oldBlocks.size();
BigInteger workDoneToSubtract = BigInteger.ZERO;
for (StoredBlock b : oldBlocks) {
workDoneToSubtract = workDoneToSubtract.add(b.getHeader().getWork());
}
log.info("DepthToSubtract = " + depthToSubtract + ", workDoneToSubtract = " + workDoneToSubtract);
// Remove depthToSubtract and workDoneToSubtract from all transactions in the wallet except for pending and inactive
// (i.e. the transactions in the two chains of blocks we are reorganising).
subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, spent.values());
subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, unspent.values());
subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, dead.values());
// The effective last seen block is now the split point so set the lastSeenBlockHash.
setLastBlockSeenHash(splitPoint.getHeader().getHash());
for (StoredBlock b : newBlocks) {
log.info("Replaying block {}", b.getHeader().getHashAsString());
Set<Transaction> txns = new HashSet<Transaction>();
Sha256Hash blockHash = b.getHeader().getHash();
for (Transaction tx : newChainTransactions.values()) {
@ -1497,11 +1530,26 @@ public class Wallet implements Serializable {
log.info(" containing tx {}", tx.getHashAsString());
}
}
for (Transaction t : txns) {
try {
receive(t, b, BlockChain.NewBlockType.BEST_CHAIN, true);
} catch (ScriptException e) {
throw new RuntimeException(e); // Cannot happen as these blocks were already verified.
if (txns.isEmpty()) {
// If there are no new transactions in this block we still need to bury existing transactions one block deeper.
for (Transaction t : spent.values()) {
t.getConfidence().notifyWorkDone(b.getHeader());
}
for (Transaction t : unspent.values()) {
t.getConfidence().notifyWorkDone(b.getHeader());
}
for (Transaction t : dead.values()) {
t.getConfidence().notifyWorkDone(b.getHeader());
}
} else {
// Add the transactions to the new blocks.
for (Transaction t : txns) {
try {
receive(t, b, BlockChain.NewBlockType.BEST_CHAIN, true);
} catch (ScriptException e) {
throw new RuntimeException(e); // Cannot happen as these blocks were already verified.
}
}
}
}
@ -1550,6 +1598,18 @@ public class Wallet implements Serializable {
checkState(isConsistent());
}
/**
* Subtract the supplied depth and work done from the given transactions.
*/
synchronized private void subtractDepthAndWorkDone(int depthToSubtract, BigInteger workDoneToSubtract, Collection<Transaction> transactions) {
for (Transaction tx : transactions) {
if (tx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING) {
tx.getConfidence().setDepthInBlocks(tx.getConfidence().getDepthInBlocks() - depthToSubtract);
tx.getConfidence().setWorkDone(tx.getConfidence().getWorkDone().subtract(workDoneToSubtract));
}
}
}
private void reprocessUnincludedTxAfterReorg(Map<Sha256Hash, Transaction> pool, Transaction tx) {
log.info("TX {}", tx.getHashAsString());
int numInputs = tx.getInputs().size();

View File

@ -201,6 +201,10 @@ public class WalletProtobufSerializer {
confidenceBuilder.setType(Protos.TransactionConfidence.Type.valueOf(confidence.getConfidenceType().getValue()));
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
confidenceBuilder.setAppearedAtHeight(confidence.getAppearedAtChainHeight());
confidenceBuilder.setDepth(confidence.getDepthInBlocks());
if (confidence.getWorkDone() != null) {
confidenceBuilder.setWorkDone(confidence.getWorkDone().longValue());
}
}
if (confidence.getConfidenceType() == ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND) {
Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash();
@ -361,6 +365,20 @@ public class WalletProtobufSerializer {
}
confidence.setAppearedAtChainHeight(confidenceProto.getAppearedAtHeight());
}
if (confidenceProto.hasDepth()) {
if (confidence.getConfidenceType() != ConfidenceType.BUILDING) {
log.warn("Have depth but not BUILDING for tx {}", tx.getHashAsString());
return;
}
confidence.setDepthInBlocks(confidenceProto.getDepth());
}
if (confidenceProto.hasWorkDone()) {
if (confidence.getConfidenceType() != ConfidenceType.BUILDING) {
log.warn("Have workDone but not BUILDING for tx {}", tx.getHashAsString());
return;
}
confidence.setWorkDone(BigInteger.valueOf(confidenceProto.getWorkDone()));
}
if (confidenceProto.hasOverridingTransaction()) {
if (confidence.getConfidenceType() != ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND) {
log.warn("Have overridingTransaction but not OVERRIDDEN for tx {}", tx.getHashAsString());

View File

@ -1784,6 +1784,14 @@ public final class Protos {
// optional bytes overriding_transaction = 3;
boolean hasOverridingTransaction();
com.google.protobuf.ByteString getOverridingTransaction();
// optional int32 depth = 4;
boolean hasDepth();
int getDepth();
// optional int64 work_done = 5;
boolean hasWorkDone();
long getWorkDone();
}
public static final class TransactionConfidence extends
com.google.protobuf.GeneratedMessage
@ -1922,10 +1930,32 @@ public final class Protos {
return overridingTransaction_;
}
// optional int32 depth = 4;
public static final int DEPTH_FIELD_NUMBER = 4;
private int depth_;
public boolean hasDepth() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
public int getDepth() {
return depth_;
}
// optional int64 work_done = 5;
public static final int WORK_DONE_FIELD_NUMBER = 5;
private long workDone_;
public boolean hasWorkDone() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
public long getWorkDone() {
return workDone_;
}
private void initFields() {
type_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Type.UNKNOWN;
appearedAtHeight_ = 0;
overridingTransaction_ = com.google.protobuf.ByteString.EMPTY;
depth_ = 0;
workDone_ = 0L;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -1948,6 +1978,12 @@ public final class Protos {
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, overridingTransaction_);
}
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeInt32(4, depth_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
output.writeInt64(5, workDone_);
}
getUnknownFields().writeTo(output);
}
@ -1969,6 +2005,14 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, overridingTransaction_);
}
if (((bitField0_ & 0x00000008) == 0x00000008)) {
size += com.google.protobuf.CodedOutputStream
.computeInt32Size(4, depth_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
size += com.google.protobuf.CodedOutputStream
.computeInt64Size(5, workDone_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -2099,6 +2143,10 @@ public final class Protos {
bitField0_ = (bitField0_ & ~0x00000002);
overridingTransaction_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004);
depth_ = 0;
bitField0_ = (bitField0_ & ~0x00000008);
workDone_ = 0L;
bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
@ -2149,6 +2197,14 @@ public final class Protos {
to_bitField0_ |= 0x00000004;
}
result.overridingTransaction_ = overridingTransaction_;
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
to_bitField0_ |= 0x00000008;
}
result.depth_ = depth_;
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000010;
}
result.workDone_ = workDone_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -2174,6 +2230,12 @@ public final class Protos {
if (other.hasOverridingTransaction()) {
setOverridingTransaction(other.getOverridingTransaction());
}
if (other.hasDepth()) {
setDepth(other.getDepth());
}
if (other.hasWorkDone()) {
setWorkDone(other.getWorkDone());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -2226,6 +2288,16 @@ public final class Protos {
overridingTransaction_ = input.readBytes();
break;
}
case 32: {
bitField0_ |= 0x00000008;
depth_ = input.readInt32();
break;
}
case 40: {
bitField0_ |= 0x00000010;
workDone_ = input.readInt64();
break;
}
}
}
}
@ -2301,6 +2373,48 @@ public final class Protos {
return this;
}
// optional int32 depth = 4;
private int depth_ ;
public boolean hasDepth() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
public int getDepth() {
return depth_;
}
public Builder setDepth(int value) {
bitField0_ |= 0x00000008;
depth_ = value;
onChanged();
return this;
}
public Builder clearDepth() {
bitField0_ = (bitField0_ & ~0x00000008);
depth_ = 0;
onChanged();
return this;
}
// optional int64 work_done = 5;
private long workDone_ ;
public boolean hasWorkDone() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
public long getWorkDone() {
return workDone_;
}
public Builder setWorkDone(long value) {
bitField0_ |= 0x00000010;
workDone_ = value;
onChanged();
return this;
}
public Builder clearWorkDone() {
bitField0_ = (bitField0_ & ~0x00000010);
workDone_ = 0L;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:wallet.TransactionConfidence)
}
@ -5673,30 +5787,31 @@ public final class Protos {
"\003 \002(\014\022\020\n\010sequence\030\004 \001(\r\"\177\n\021TransactionOu" +
"tput\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(" +
"\014\022!\n\031spent_by_transaction_hash\030\003 \001(\014\022\"\n\032",
"spent_by_transaction_index\030\004 \001(\005\"\366\001\n\025Tra" +
"spent_by_transaction_index\030\004 \001(\005\"\230\002\n\025Tra" +
"nsactionConfidence\0220\n\004type\030\001 \001(\0162\".walle" +
"t.TransactionConfidence.Type\022\032\n\022appeared" +
"_at_height\030\002 \001(\005\022\036\n\026overriding_transacti" +
"on\030\003 \001(\014\"o\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDIN" +
"G\020\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BES" +
"T_CHAIN\020\003\022\036\n\032OVERRIDDEN_BY_DOUBLE_SPEND\020" +
"\004\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n\004ha" +
"sh\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Transact" +
"ion.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_a",
"t\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030.wal" +
"let.TransactionInput\0225\n\022transaction_outp" +
"ut\030\007 \003(\0132\031.wallet.TransactionOutput\022\022\n\nb" +
"lock_hash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132\035.wa" +
"llet.TransactionConfidence\"Y\n\004Pool\022\013\n\007UN" +
"SPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004DEAD" +
"\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\"8\n" +
"\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021\n\t" +
"mandatory\030\003 \002(\010\"\254\001\n\006Wallet\022\032\n\022network_id" +
"entifier\030\001 \002(\t\022\034\n\024last_seen_block_hash\030\002",
" \001(\014\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transa" +
"ction\030\004 \003(\0132\023.wallet.Transaction\022$\n\texte" +
"nsion\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org.bi" +
"tcoinj.walletB\006Protos"
"on\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022\021\n\twork_done\030\005 \001" +
"(\003\"o\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022\025\n" +
"\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BEST_CHAI" +
"N\020\003\022\036\n\032OVERRIDDEN_BY_DOUBLE_SPEND\020\004\"\211\003\n\013" +
"Transaction\022\017\n\007version\030\001 \002(\005\022\014\n\004hash\030\002 \002" +
"(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Transaction.Po",
"ol\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(" +
"\003\0223\n\021transaction_input\030\006 \003(\0132\030.wallet.Tr" +
"ansactionInput\0225\n\022transaction_output\030\007 \003" +
"(\0132\031.wallet.TransactionOutput\022\022\n\nblock_h" +
"ash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132\035.wallet.T" +
"ransactionConfidence\"Y\n\004Pool\022\013\n\007UNSPENT\020" +
"\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007" +
"PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\"8\n\tExten" +
"sion\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021\n\tmandat" +
"ory\030\003 \002(\010\"\254\001\n\006Wallet\022\032\n\022network_identifi",
"er\030\001 \002(\t\022\034\n\024last_seen_block_hash\030\002 \001(\014\022\030" +
"\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transaction\030" +
"\004 \003(\0132\023.wallet.Transaction\022$\n\textension\030" +
"\n \003(\0132\021.wallet.ExtensionB\035\n\023org.bitcoinj" +
".walletB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -5732,7 +5847,7 @@ public final class Protos {
internal_static_wallet_TransactionConfidence_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_wallet_TransactionConfidence_descriptor,
new java.lang.String[] { "Type", "AppearedAtHeight", "OverridingTransaction", },
new java.lang.String[] { "Type", "AppearedAtHeight", "OverridingTransaction", "Depth", "WorkDone", },
org.bitcoinj.wallet.Protos.TransactionConfidence.class,
org.bitcoinj.wallet.Protos.TransactionConfidence.Builder.class);
internal_static_wallet_Transaction_descriptor =

View File

@ -26,11 +26,12 @@ import java.util.ArrayList;
import static org.junit.Assert.*;
public class ChainSplitTests {
public class ChainSplitTest {
private NetworkParameters unitTestParams;
private Wallet wallet;
private BlockChain chain;
private Address coinsTo;
private Address coinsTo2;
private Address someOtherGuy;
@Before
@ -39,8 +40,10 @@ public class ChainSplitTests {
unitTestParams = NetworkParameters.unitTests();
wallet = new Wallet(unitTestParams);
wallet.addKey(new ECKey());
wallet.addKey(new ECKey());
chain = new BlockChain(unitTestParams, wallet, new MemoryBlockStore(unitTestParams));
coinsTo = wallet.keychain.get(0).toAddress(unitTestParams);
coinsTo2 = wallet.keychain.get(1).toAddress(unitTestParams);
someOtherGuy = new ECKey().toAddress(unitTestParams);
}
@ -283,7 +286,7 @@ public class ChainSplitTests {
public void txConfidenceLevels() throws Exception {
// Check that as the chain forks and re-orgs, the confidence data associated with each transaction is
// maintained correctly.
final ArrayList<Transaction> txns = new ArrayList<Transaction>(2);
final ArrayList<Transaction> txns = new ArrayList<Transaction>(3);
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
@ -291,62 +294,125 @@ public class ChainSplitTests {
}
});
// Start by building a couple of blocks on top of the genesis block.
// Start by building three blocks on top of the genesis block.
Block b1 = unitTestParams.genesisBlock.createNextBlock(coinsTo);
Block b2 = b1.createNextBlock(coinsTo);
BigInteger work1 = b1.getWork();
Block b2 = b1.createNextBlock(coinsTo2);
BigInteger work2 = b2.getWork();
Block b3 = b2.createNextBlock(coinsTo2);
BigInteger work3 = b3.getWork();
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
assertTrue(chain.add(b3));
// Check the transaction confidence levels are correct.
assertEquals(2, txns.size());
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(1, txns.get(1).getConfidence().getDepthInBlocks(chain));
assertEquals(2, txns.get(0).getConfidence().getDepthInBlocks(chain));
assertEquals(10, txns.get(0).getConfidence().getWorkDone(chain).intValue());
assertEquals(6, txns.get(1).getConfidence().getWorkDone(chain).intValue());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work2).add(work3), txns.get(0).getConfidence().getWorkDone());
assertEquals(work2.add(work3), txns.get(1).getConfidence().getWorkDone());
assertEquals(work3, txns.get(2).getConfidence().getWorkDone());
// We now have the following chain:
// genesis -> b1 -> b2
// genesis -> b1 -> b2 -> b3
//
// so fork like this:
//
// genesis -> b1 -> b2
// \-> b3
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5
//
// Nothing should happen at this point. We saw b2 first so it takes priority.
Block b3 = b1.createNextBlock(someOtherGuy);
assertTrue(chain.add(b3));
assertEquals(2, txns.size());
// Nothing should happen at this point. We saw b2 and b3 first so it takes priority.
Block b4 = b1.createNextBlock(someOtherGuy);
BigInteger work4 = b4.getWork();
Block b5 = b4.createNextBlock(someOtherGuy);
BigInteger work5 = b5.getWork();
assertTrue(chain.add(b4));
assertTrue(chain.add(b5));
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work2).add(work3), txns.get(0).getConfidence().getWorkDone());
assertEquals(work2.add(work3), txns.get(1).getConfidence().getWorkDone());
assertEquals(work3, txns.get(2).getConfidence().getWorkDone());
// Now we add another block to make the alternative chain longer.
assertTrue(chain.add(b3.createNextBlock(someOtherGuy)));
Block b6 = b5.createNextBlock(someOtherGuy);
BigInteger work6 = b6.getWork();
assertTrue(chain.add(b6));
//
// genesis -> b1 -> b2
// \-> b3 -> b4
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5 -> b6
//
assertEquals(2, txns.size());
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(4, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work4).add(work5).add(work6), txns.get(0).getConfidence().getWorkDone());
// Transaction 1 (in block b2) is now on a side chain.
assertEquals(TransactionConfidence.ConfidenceType.NOT_IN_BEST_CHAIN, txns.get(1).getConfidence().getConfidenceType());
try {
txns.get(1).getConfidence().getAppearedAtChainHeight();
fail();
} catch (IllegalStateException e) {}
try {
txns.get(1).getConfidence().getDepthInBlocks();
fail();
} catch (IllegalStateException e) {}
try {
txns.get(1).getConfidence().getWorkDone();
fail();
} catch (IllegalStateException e) {}
// ... and back to the first chain.
Block b5 = b2.createNextBlock(coinsTo);
Block b6 = b5.createNextBlock(coinsTo);
assertTrue(chain.add(b5));
assertTrue(chain.add(b6));
Block b7 = b3.createNextBlock(coinsTo);
BigInteger work7 = b7.getWork();
Block b8 = b7.createNextBlock(coinsTo);
BigInteger work8 = b7.getWork();
assertTrue(chain.add(b7));
assertTrue(chain.add(b8));
//
// genesis -> b1 -> b2 -> b5 -> b6
// \-> b3 -> b4
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
// \-> b4 -> b5 -> b6
//
// This should be enabled, once we figure out the best way to inform the user of how the wallet is changing
// during the re-org.
// assertEquals(4, txns.size());
//assertEquals(5, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(5, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(4, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(3, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work2).add(work3).add(work7).add(work8), txns.get(0).getConfidence().getWorkDone());
assertEquals(work2.add(work3).add(work7).add(work8), txns.get(1).getConfidence().getWorkDone());
assertEquals(work3.add(work7).add(work8), txns.get(2).getConfidence().getWorkDone());
assertEquals("250.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
}

View File

@ -653,7 +653,6 @@ public class WalletTest {
// Now check we can serialize old wallets to protocol buffers. Covers bug 134.
bios.reset();
new WalletProtobufSerializer().writeWallet(wallet, bios);
}
@Test

View File

@ -17,6 +17,9 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import static com.google.bitcoin.core.TestUtils.createFakeTx;
import static org.junit.Assert.*;
@ -128,7 +131,7 @@ public class WalletProtobufSerializerTest {
Sha256Hash blockHash = block.getHash();
wallet.setLastBlockSeenHash(blockHash);
// Roundtrip the wallet and check it has stored te blockHash.
// Roundtrip the wallet and check it has stored the blockHash.
Wallet wallet1 = roundTrip(wallet);
assertEquals(blockHash, wallet1.getLastBlockSeenHash());
@ -139,6 +142,83 @@ public class WalletProtobufSerializerTest {
assertEquals(genesisBlock.getHash(), wallet2.getLastBlockSeenHash());
}
@Test
public void testAppearedAtChainHeightDepthAndWorkDone() throws Exception {
// Test the TransactionConfidence appearedAtChainHeight, depth and workDone field are stored.
BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params));
final ArrayList<Transaction> txns = new ArrayList<Transaction>(2);
myWallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
txns.add(tx);
}
});
// Start by building two blocks on top of the genesis block.
Block b1 = params.genesisBlock.createNextBlock(myAddress);
BigInteger work1 = b1.getWork();
assertTrue(work1.compareTo(BigInteger.ZERO) > 0);
Block b2 = b1.createNextBlock(myAddress);
BigInteger work2 = b2.getWork();
assertTrue(work2.compareTo(BigInteger.ZERO) > 0);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
// We now have the following chain:
// genesis -> b1 -> b2
// Check the transaction confidence levels are correct before wallet roundtrip.
assertEquals(2, txns.size());
TransactionConfidence confidence0 = txns.get(0).getConfidence();
TransactionConfidence confidence1 = txns.get(1).getConfidence();
assertEquals(1, confidence0.getAppearedAtChainHeight());
assertEquals(2, confidence1.getAppearedAtChainHeight());
assertEquals(2, confidence0.getDepthInBlocks());
assertEquals(1, confidence1.getDepthInBlocks());
assertEquals(work1.add(work2), confidence0.getWorkDone());
assertEquals(work2, confidence1.getWorkDone());
// Roundtrip the wallet and check it has stored the depth and workDone.
Wallet rebornWallet = roundTrip(myWallet);
Set<Transaction> rebornTxns = rebornWallet.getTransactions(false, false);
assertEquals(2, rebornTxns.size());
// The transactions are not guaranteed to be in the same order so sort them to be in chain height order if required.
Iterator<Transaction> it = rebornTxns.iterator();
Transaction txA = it.next();
Transaction txB = it.next();
Transaction rebornTx0, rebornTx1;
if (txA.getConfidence().getAppearedAtChainHeight() == 1) {
rebornTx0 = txA;
rebornTx1 = txB;
} else {
rebornTx0 = txB;
rebornTx1 = txA;
}
TransactionConfidence rebornConfidence0 = rebornTx0.getConfidence();
TransactionConfidence rebornConfidence1 = rebornTx1.getConfidence();
assertEquals(1, rebornConfidence0.getAppearedAtChainHeight());
assertEquals(2, rebornConfidence1.getAppearedAtChainHeight());
assertEquals(2, rebornConfidence0.getDepthInBlocks());
assertEquals(1, rebornConfidence1.getDepthInBlocks());
assertEquals(work1.add(work2), rebornConfidence0.getWorkDone());
assertEquals(work2, rebornConfidence1.getWorkDone());
}
private Wallet roundTrip(Wallet wallet) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
//System.out.println(WalletProtobufSerializer.walletToText(wallet));

View File

@ -125,13 +125,9 @@ public class PingService {
System.out.println("Confidences of wallet transactions:");
for (Transaction tx : transactions) {
System.out.println(tx);
try {
System.out.println(tx.getConfidence());
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
System.out.println("Work done: " + tx.getConfidence().getWorkDone(chain).toString());
} catch (BlockStoreException e) {
throw new RuntimeException(e);
}
System.out.println(tx.getConfidence());
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
System.out.println("Work done: " + tx.getConfidence().getWorkDone().toString());
System.out.println();
}
}