mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-09 16:04:54 +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