diff --git a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java index d39afc1a..15ab6a74 100644 --- a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java +++ b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java @@ -624,9 +624,6 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca checkState(!wallets.contains(wallet)); wallets.add(wallet); wallet.setTransactionBroadcaster(this); - // TODO: Make wallets announce their own pending transactions. - // The only reason it's not done that way now is to try and reduce late, risky changes before 0.10 - announcePendingWalletTransactions(Collections.singletonList(wallet), peers); wallet.addEventListener(walletEventListener); // TODO: Run this in the current peer thread. addPeerFilterProvider(wallet); } finally { @@ -666,6 +663,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca wallets.remove(checkNotNull(wallet)); peerFilterProviders.remove(wallet); wallet.removeEventListener(walletEventListener); + wallet.setTransactionBroadcaster(null); } /** @@ -869,16 +867,6 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca } // Make sure the peer knows how to upload transactions that are requested from us. peer.addEventListener(getDataListener, Threading.SAME_THREAD); - // Now tell the peers about any transactions we have which didn't appear in the chain yet. These are not - // necessarily spends we created. They may also be transactions broadcast across the network that we saw, - // which are relevant to us, and which we therefore wish to help propagate (ie they send us coins). - // - // Note that this can cause a DoS attack against us if a malicious remote peer knows what keys we own, and - // then sends us fake relevant transactions. We'll attempt to relay the bad transactions, our badness score - // in the Satoshi client will increase and we'll get disconnected. - // - // TODO: Find a way to balance the desire to propagate useful transactions against DoS attacks. - announcePendingWalletTransactions(wallets, Collections.singletonList(peer)); // And set up event listeners for clients. This will allow them to find out about new transactions and blocks. for (ListenerRegistration registration : peerEventListeners) { peer.addEventListener(registration.listener, registration.executor); @@ -951,30 +939,6 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca pingRunnable[0].run(); } - /** Returns true if at least one peer received an inv. */ - private boolean announcePendingWalletTransactions(List announceWallets, - List announceToPeers) { - checkState(lock.isHeldByCurrentThread()); - // Build up an inv announcing the hashes of all pending transactions in all our wallets. - InventoryMessage inv = new InventoryMessage(params); - for (Wallet w : announceWallets) { - for (Transaction tx : w.getPendingTransactions()) { - inv.addTransaction(tx); - } - } - // Don't send empty inv messages. - if (inv.getItems().size() == 0) { - return true; - } - boolean success = false; - for (Peer p : announceToPeers) { - log.info("{}: Announcing {} pending wallet transactions", p.getAddress(), inv.getItems().size()); - p.sendMessage(inv); - success = true; - } - return success; - } - private void setDownloadPeer(Peer peer) { lock.lock(); try { diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index e7add45c..5e6cb711 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -3275,7 +3275,24 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi * optimise itself to reduce fees or improve privacy.

*/ public void setTransactionBroadcaster(@Nullable com.google.bitcoin.core.TransactionBroadcaster broadcaster) { - vTransactionBroadcaster = broadcaster; + lock.lock(); + try { + if (vTransactionBroadcaster == broadcaster) + return; + vTransactionBroadcaster = broadcaster; + if (broadcaster == null) + return; + // Now use it to upload any pending transactions we have that are marked as not being seen by any peers yet. + for (Transaction tx : pending.values()) { + checkState(tx.getConfidence().getConfidenceType() == ConfidenceType.PENDING); + if (tx.getConfidence().numBroadcastPeers() == 0) { + log.info("New broadcaster so uploading waiting tx {}", tx.getHash()); + broadcaster.broadcastTransaction(tx); + } + } + } finally { + lock.unlock(); + } } /** diff --git a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java index d9bd531e..7116dad3 100644 --- a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java +++ b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import static com.google.common.base.Preconditions.checkNotNull; import static org.junit.Assert.*; public class PeerGroupTest extends TestWithPeerGroup { @@ -303,7 +304,7 @@ public class PeerGroupTest extends TestWithPeerGroup { Threading.waitForUserCode(); assertTrue(sendResult.broadcastComplete.isDone()); assertEquals(transactions[0], sendResult.tx); - assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 2); + assertEquals(2, transactions[0].getConfidence().numBroadcastPeers()); // Confirm it. Block b2 = TestUtils.createFakeBlock(blockStore, t1).block; inbound(p1, b2); @@ -313,24 +314,12 @@ public class PeerGroupTest extends TestWithPeerGroup { peerGroup.removeWallet(wallet); Wallet.SendRequest req = Wallet.SendRequest.to(dest, Utils.toNanoCoins(2, 0)); req.ensureMinRequiredFee = false; - Transaction t3 = wallet.sendCoinsOffline(req); + Transaction t3 = checkNotNull(wallet.sendCoinsOffline(req)); assertNull(outbound(p1)); // Nothing sent. // Add the wallet to the peer group (simulate initialization). Transactions should be announced. peerGroup.addWallet(wallet); // Transaction announced to the first peer. - InventoryMessage inv1 = (InventoryMessage) outbound(p1); - // Filter is still the same as it was, so it is not rebroadcast - assertEquals(t3.getHash(), inv1.getItems().get(0).hash); - // Peer asks for the transaction, and get it. - GetDataMessage getdata = new GetDataMessage(params); - getdata.addItem(inv1.getItems().get(0)); - inbound(p1, getdata); - Transaction t4 = (Transaction) outbound(p1); - assertEquals(t3, t4); - - FakeChannel p3 = connectPeer(3); - assertTrue(outbound(p3) instanceof InventoryMessage); - control.verify(); + assertEquals(t3.getHash(), ((Transaction) outbound(p1)).getHash()); } @Test diff --git a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java index 3db655ef..d04ebc65 100644 --- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java +++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java @@ -69,6 +69,7 @@ public class WalletTool { " --force Overrides any safety checks on the requested action.\n" + " --date Provide a date in form YYYY/MM/DD to any action that requires one.\n" + " --peers=1.2.3.4 Comma separated IP addresses/domain names for connections instead of peer discovery.\n" + + " --offline If specified when sending, don't try and connect, just write the tx to the wallet.\n" + " --condition=... Allows you to specify a numeric condition for other commands. The format is\n" + " one of the following operators = < > <= >= immediately followed by a number.\n" + " For example --condition=\">5.10\" or --condition=\"<=1\"\n" + @@ -259,6 +260,7 @@ public class WalletTool { conditionFlag = parser.accepts("condition").withRequiredArg(); parser.accepts("locktime").withRequiredArg(); parser.accepts("allow-unconfirmed"); + parser.accepts("offline"); OptionSpec passwordFlag = parser.accepts("password").withRequiredArg(); options = parser.parse(args); @@ -397,7 +399,7 @@ public class WalletTool { shutdown(); } - private static void send(List outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) { + private static void send(List outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException { try { // Convert the input strings to outputs. Transaction t = new Transaction(params); @@ -467,6 +469,12 @@ public class WalletTool { throw new RuntimeException(e); } t = req.tx; // Not strictly required today. + System.out.println(t.getHashAsString()); + if (options.has("offline")) { + wallet.commitTx(t); + return; + } + setup(); peers.startAndWait(); // Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the @@ -478,7 +486,6 @@ public class WalletTool { // completes before the remote peer actually hears the message. Thread.sleep(5000); } - System.out.println(t.getHashAsString()); } catch (BlockStoreException e) { throw new RuntimeException(e); } catch (KeyCrypterException e) {