Rename and rewrite PingService. It

It's now just ForwardingService, doesn't use the "from address" concept anymore, and uses WalletAppKit + balance futures. The new code is much simpler and easier to read.
This commit is contained in:
Mike Hearn 2013-07-29 17:19:28 +02:00
parent f821207a80
commit 9adb275e6d
2 changed files with 153 additions and 213 deletions

View file

@ -0,0 +1,153 @@
/**
* Copyright 2011 Google Inc.
*
* 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 com.google.bitcoin.examples;
import com.google.bitcoin.core.*;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.kits.WalletAppKit;
import com.google.bitcoin.params.MainNetParams;
import com.google.bitcoin.params.RegTestParams;
import com.google.bitcoin.params.TestNet3Params;
import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.File;
import java.math.BigInteger;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* ForwardingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply
* sends them onwards to an address given on the command line.
*/
public class ForwardingService {
private static Address forwardingAddress;
private static WalletAppKit kit;
public static void main(String[] args) throws Exception {
// This line makes the log output more compact and easily read, especially when using the JDK log adapter.
BriefLogFormatter.init();
if (args.length < 2) {
System.err.println("Usage: address-to-send-back-to [regtest|testnet]");
return;
}
// Figure out which network we should connect to. Each one gets its own set of files.
NetworkParameters params;
String filePrefix;
if (args[1].equals("testnet")) {
params = TestNet3Params.get();
filePrefix = "forwarding-service-testnet";
} else if (args[1].equals("regtest")) {
params = RegTestParams.get();
filePrefix = "forwarding-service-regtest";
} else {
params = MainNetParams.get();
filePrefix = "forwarding-service";
}
// Parse the address given as the first parameter.
forwardingAddress = new Address(params, args[0]);
// Start up a basic app using a class that automates some boilerplate. Ensure we always have at least one key.
kit = new WalletAppKit(params, new File("."), filePrefix) {
@Override
protected void onSetupCompleted() {
// This is called in a background thread after startAndWait is called, as setting up various objects
// can do disk and network IO that may cause UI jank/stuttering in wallet apps if it were to be done
// on the main thread.
if (wallet().getKeychainSize() < 1)
wallet().addKey(new ECKey());
}
};
if (params == RegTestParams.get()) {
// Regression test mode is designed for testing and development only, so there's no public network for it.
// If you pick this mode, you're expected to be running a local "bitcoind -regtest" instance.
kit.connectToLocalHost();
}
// Download the block chain and wait until it's done.
kit.startAndWait();
// We want to know when we receive money.
kit.wallet().addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
// Runs in the dedicated "user thread" (see bitcoinj docs for more info on this).
//
// The transaction "tx" can either be pending, or included into a block (we didn't see the broadcast).
BigInteger value = tx.getValueSentToMe(w);
System.out.println("Received tx for " + Utils.bitcoinValueToFriendlyString(value) + ": " + tx);
System.out.println("Transaction will be forwarded after it confirms.");
// Wait until it's made it into the block chain (may run immediately if it's already there).
//
// For this dummy app of course, we could just forward the unconfirmed transaction. If it were
// to be double spent, no harm done. Wallet.allowSpendingUnconfirmedTransactions() would have to
// be called in onSetupCompleted() above. But we don't do that here to demonstrate the more common
// case of waiting for a block.
Futures.addCallback(tx.getConfidence().getDepthFuture(1), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {
// "result" here is the same as "tx" above, but we use it anyway for clarity.
forwardCoins(result);
}
@Override
public void onFailure(Throwable t) {
// This kind of future can't fail, just rethrow in case something weird happens.
throw new RuntimeException(t);
}
});
}
});
Address sendToAddress = kit.wallet().getKeys().get(0).toAddress(params);
System.out.println("Send coins to: " + sendToAddress);
System.out.println("Waiting for coins to arrive. Press Ctrl-C to quit.");
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ignored) {}
}
private static void forwardCoins(Transaction tx) {
try {
BigInteger value = tx.getValueSentToMe(kit.wallet());
System.out.println("Forwarding " + Utils.bitcoinValueToFriendlyString(value) + " BTC");
// Now send the coins back! Send with a small fee attached to ensure rapid confirmation.
final BigInteger amountToSend = value.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
final Wallet.SendResult sendResult = kit.wallet().sendCoins(kit.peerGroup(), forwardingAddress, amountToSend);
checkNotNull(sendResult); // We should never try to send more coins than we have!
System.out.println("Sending ...");
// Register a callback that is invoked when the transaction has propagated across the network.
// This shows a second style of registering ListenableFuture callbacks, it works when you don't
// need access to the object the future returns.
sendResult.broadcastComplete.addListener(new Runnable() {
@Override
public void run() {
// The wallet has changed now, it'll get auto saved shortly or when the app shuts down.
System.out.println("Sent coins onwards! Transaction hash is " + sendResult.tx.getHashAsString());
}
}, MoreExecutors.sameThreadExecutor());
} catch (KeyCrypterException e) {
// We don't use encrypted wallets in this example - can never happen.
throw new RuntimeException(e);
}
}
}

View file

@ -1,213 +0,0 @@
/**
* Copyright 2011 Google Inc.
*
* 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 com.google.bitcoin.examples;
import com.google.bitcoin.core.*;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.discovery.DnsDiscovery;
import com.google.bitcoin.params.MainNetParams;
import com.google.bitcoin.params.RegTestParams;
import com.google.bitcoin.params.TestNet3Params;
import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.SPVBlockStore;
import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* <p>
* PingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply
* sends them right back to the previous owner, determined rather arbitrarily by the address of the first input.
* </p>
*
* <p>
* If running on TestNet (slow but better than using real coins on the main network) do the following:
* <ol>
* <li>Backup your current wallet.dat in case of unforeseen problems</li>
* <li>Start your bitcoin client in test mode <code>bitcoin -testnet</code>. This will create a new sub-directory called testnet and should not interfere with normal wallets or operations.</li>
* <li>(Optional) Choose a fresh address</li>
* <li>(Optional) Visit the Testnet faucet (https://testnet.freebitcoins.appspot.com/) to load your client with test coins</li>
* <li>Run <code>PingService testnet</code></li>
* <li>Wait for the block chain to download</li>
* <li>Send some coins from your bitcoin client to the address provided in the PingService console</li>
* <li>Leave it running until you get the coins back again</li>
* </ol>
* </p>
*
* <p>The testnet can be slow or flaky as it's a shared resource. You can use the <a href="http://sourceforge
* .net/projects/bitcoin/files/Bitcoin/testnet-in-a-box/">testnet in a box</a> to do everything purely locally.</p>
*/
public class PingService {
private final PeerGroup peerGroup;
private final BlockStore blockStore;
private final File walletFile;
private final Address sendToAddress;
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
System.out.println("Usage: pingservice address-to-send-back-to [regtest|testnet]");
new PingService(args);
}
public PingService(String[] args) throws Exception {
NetworkParameters params;
String filePrefix;
if (args[1].equals("testnet")) {
params = TestNet3Params.get();
filePrefix = "pingservice-testnet";
} else if (args[1].equals("regtest")) {
params = RegTestParams.get();
filePrefix = "pingservice-regtest";
} else {
params = MainNetParams.get();
filePrefix = "pingservice";
}
sendToAddress = new Address(params, args[0]);
File chainFile = new File(filePrefix + ".spvchain");
if (params == RegTestParams.get() && chainFile.exists()) {
chainFile.delete();
}
// Try to read the wallet from storage, create a new one if not possible.
Wallet w = null;
walletFile = new File(filePrefix + ".wallet");
try {
// Wipe the wallet if the chain file was deleted.
if (walletFile.exists() && chainFile.exists())
w = Wallet.loadFromFile(walletFile);
} catch (UnreadableWalletException e) {
System.err.println("Couldn't load wallet: " + e);
// Fall through.
}
if (w == null) {
System.out.println("Creating new wallet file.");
w = new Wallet(params);
w.addKey(new ECKey());
w.saveToFile(walletFile);
}
final Wallet wallet = w;
// Fetch the first key in the wallet (should be the only key).
ECKey key = wallet.getKeys().iterator().next();
// Load the block chain, if there is one stored locally. If it's going to be freshly created, checkpoint it.
System.out.println("Reading block store from disk");
boolean chainExistedAlready = chainFile.exists();
blockStore = new SPVBlockStore(params, chainFile);
if (!chainExistedAlready) {
File checkpointsFile = new File("checkpoints");
if (checkpointsFile.exists()) {
FileInputStream stream = new FileInputStream(checkpointsFile);
CheckpointManager.checkpoint(params, stream, blockStore, key.getCreationTimeSeconds());
}
}
BlockChain chain = new BlockChain(params, wallet, blockStore);
// Connect to the localhost node. One minute timeout since we won't try any other peers
System.out.println("Connecting ...");
peerGroup = new PeerGroup(params, chain);
peerGroup.setUserAgent("PingService", "1.0");
if (params == RegTestParams.get()) {
peerGroup.addAddress(InetAddress.getLocalHost());
} else {
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
}
peerGroup.addWallet(wallet);
// We want to know when the balance changes.
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
assert !newBalance.equals(BigInteger.ZERO);
if (!tx.isPending()) return;
// It was broadcast, but we can't really verify it's valid until it appears in a block.
BigInteger value = tx.getValueSentToMe(w);
System.out.println("Received pending tx for " + Utils.bitcoinValueToFriendlyString(value) +
": " + tx);
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(final Transaction tx2, TransactionConfidence.Listener.ChangeReason reason) {
if (tx2.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
// Coins were confirmed (appeared in a block).
tx2.getConfidence().removeEventListener(this);
forwardCoins(wallet, tx2);
} else {
System.out.println(String.format("Confidence of %s changed, is now: %s",
tx2.getHashAsString(), tx2.getConfidence().toString()));
}
}
});
}
});
peerGroup.startAndWait();
// Now make sure that we shut down cleanly!
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override public void run() {
try {
System.out.print("Shutting down ... ");
peerGroup.stopAndWait();
wallet.saveToFile(walletFile);
blockStore.close();
System.out.print("done ");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
peerGroup.downloadBlockChain();
System.out.println("Send coins to: " + key.toAddress(params).toString());
System.out.println("Waiting for coins to arrive. Press Ctrl-C to quit.");
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
private void forwardCoins(final Wallet wallet, Transaction tx) {
try {
BigInteger value = tx.getValueSentToMe(wallet);
TransactionInput input = tx.getInputs().get(0);
System.out.println("Received " + Utils.bitcoinValueToFriendlyString(value));
// Now send the coins back! Send with a fee attached to ensure rapid confirmation.
final BigInteger amountToSend = value.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
final Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, sendToAddress, amountToSend);
checkNotNull(sendResult); // We should never try to send more coins than we have!
System.out.println("Sending ...");
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
public void onSuccess(Transaction transaction) {
System.out.println("Sent coins back! Transaction hash is " + sendResult.tx.getHashAsString());
// The wallet has changed now, it'll get auto saved shortly or when the app shuts down.
}
public void onFailure(Throwable throwable) {
System.err.println("Failed to send coins :(");
throwable.printStackTrace();
}
});
} catch (KeyCrypterException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}