From 4886a137bc3835a64f7591a76d12da0078de3a2a Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 6 May 2013 17:59:31 +0200 Subject: [PATCH] TransactionConfidence: make accessors return zero rather than throw when pending, and add a method to get a depth future. --- .../com/google/bitcoin/core/Transaction.java | 10 +--- .../bitcoin/core/TransactionConfidence.java | 52 ++++++++++++------- .../google/bitcoin/core/ChainSplitTest.java | 10 +--- .../com/google/bitcoin/core/WalletTest.java | 3 ++ 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index fa7f61ab..678628fd 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -265,20 +265,14 @@ public class Transaction extends ChildMessage implements Serializable { // This can cause event listeners on TransactionConfidence to run. After these lines complete, the wallets // state may have changed! TransactionConfidence transactionConfidence = getConfidence(); - transactionConfidence.setAppearedAtChainHeight(block.getHeight()); - - // Reset the confidence block depth. - transactionConfidence.setDepthInBlocks(1); - // Reset the work done. try { transactionConfidence.setWorkDone(block.getHeader().getWork()); } catch (VerificationException e) { throw new RuntimeException(e); // Cannot happen. } - - // The transaction is now on the best chain. - transactionConfidence.setConfidenceType(ConfidenceType.BUILDING); + // This sets type to BUILDING and depth to one. + transactionConfidence.setAppearedAtChainHeight(block.getHeight()); } } diff --git a/core/src/main/java/com/google/bitcoin/core/TransactionConfidence.java b/core/src/main/java/com/google/bitcoin/core/TransactionConfidence.java index 5545ca24..7f6e0af5 100644 --- a/core/src/main/java/com/google/bitcoin/core/TransactionConfidence.java +++ b/core/src/main/java/com/google/bitcoin/core/TransactionConfidence.java @@ -17,6 +17,8 @@ package com.google.bitcoin.core; import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import java.io.Serializable; import java.math.BigInteger; @@ -191,12 +193,13 @@ public class TransactionConfidence implements Serializable { /** * The chain height at which the transaction appeared, if it has been seen in the best chain. Automatically sets - * the current type to {@link ConfidenceType#BUILDING}. + * the current type to {@link ConfidenceType#BUILDING} and depth to one. */ public synchronized void setAppearedAtChainHeight(int appearedAtChainHeight) { if (appearedAtChainHeight < 0) throw new IllegalArgumentException("appearedAtChainHeight out of range"); this.appearedAtChainHeight = appearedAtChainHeight; + this.depth = 1; setConfidenceType(ConfidenceType.BUILDING); } @@ -314,23 +317,16 @@ public class TransactionConfidence implements Serializable { } /** - * 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 + *

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 * (Satoshi) implementation considers a transaction impractical to reverse after 6 blocks, but as of EOY 2011 network * security is high enough that often only one block is considered enough even for high value transactions. For low - * value transactions like songs, or other cheap items, no blocks at all may be necessary.

+ * value transactions like songs, or other cheap items, no blocks at all may be necessary.

* - * If the transaction appears in the top block, the depth is one. If the transaction does not appear in the best - * chain yet, throws IllegalStateException, so use {@link com.google.bitcoin.core.TransactionConfidence#getConfidenceType()} - * to check first. - * - * @throws IllegalStateException if confidence type != BUILDING. - * @return depth + *

If the transaction appears in the top block, the depth is one. If it's anything else (pending, dead, unknown) + * the depth is zero.

*/ public synchronized int getDepthInBlocks() { - if (getConfidenceType() != ConfidenceType.BUILDING) { - throw new IllegalStateException("Confidence type is not BUILDING"); - } return depth; } @@ -345,15 +341,10 @@ public class TransactionConfidence implements Serializable { * Returns the estimated amount of work (number of hashes performed) on this transaction. Work done is a measure of * security that is related to depth in blocks, but more predictable: the network will always attempt to produce six * 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. - * - * @throws IllegalStateException if confidence type is not BUILDING + * reverse a transaction, counting blocks is not enough. If a transaction has not confirmed, the result is zero. * @return estimated number of hashes needed to reverse the transaction. */ public synchronized BigInteger getWorkDone() { - if (getConfidenceType() != ConfidenceType.BUILDING) { - throw new IllegalStateException("Confidence type is not BUILDING"); - } return workDone; } @@ -423,4 +414,27 @@ public class TransactionConfidence implements Serializable { public synchronized void setSource(Source source) { this.source = source; } + + /** + * Returns a future that completes when the transaction has been confirmed by "depth" blocks. For instance setting + * depth to one will wait until it appears in a block on the best chain, and zero will wait until it has been seen + * on the network. + */ + public ListenableFuture getDepthFuture(final int depth) { + final SettableFuture result = SettableFuture.create(); + synchronized (this) { + if (getDepthInBlocks() >= depth) { + result.set(transaction); + } + addEventListener(new Listener() { + @Override public void onConfidenceChanged(Transaction tx) { + if (getDepthInBlocks() >= depth) { + removeEventListener(this); + result.set(transaction); + } + } + }); + } + return result; + } } diff --git a/core/src/test/java/com/google/bitcoin/core/ChainSplitTest.java b/core/src/test/java/com/google/bitcoin/core/ChainSplitTest.java index b417616f..9c9d4586 100644 --- a/core/src/test/java/com/google/bitcoin/core/ChainSplitTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ChainSplitTest.java @@ -449,14 +449,8 @@ public class ChainSplitTest { 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) {} + assertEquals(0, txns.get(1).getConfidence().getDepthInBlocks()); + assertEquals(BigInteger.ZERO, txns.get(1).getConfidence().getWorkDone()); // ... and back to the first chain. Block b7 = b3.createNextBlock(coinsTo); diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java index 3a4c56bc..89cd708e 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -181,6 +181,8 @@ public class WalletTest extends TestWithWallet { assertFalse(availFuture.isDone()); assertFalse(estimatedFuture.isDone()); Transaction t1 = sendMoneyToWallet(wallet, v1, toAddress, null); + final ListenableFuture depthFuture = t1.getConfidence().getDepthFuture(1); + assertFalse(depthFuture.isDone()); assertEquals(BigInteger.ZERO, wallet.getBalance()); assertEquals(v1, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertFalse(availFuture.isDone()); @@ -194,6 +196,7 @@ public class WalletTest extends TestWithWallet { assertEquals("Incorrect confirmed tx ALL pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); assertTrue(availFuture.isDone()); assertTrue(estimatedFuture.isDone()); + assertTrue(depthFuture.isDone()); } private void basicSanityChecks(Wallet wallet, Transaction t, Address fromAddress, Address destination) throws ScriptException {