mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-09 05:14:46 +01:00
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:
parent
f821207a80
commit
9adb275e6d
2 changed files with 153 additions and 213 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue