/**
* 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.litecoin.examples;
import com.google.litecoin.core.*;
import com.google.litecoin.crypto.KeyCrypterException;
import com.google.litecoin.discovery.DnsDiscovery;
import com.google.litecoin.discovery.IrcDiscovery;
import com.google.litecoin.store.BlockStore;
import com.google.litecoin.store.SPVBlockStore;
import com.google.litecoin.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.io.IOException;
import java.math.BigInteger;
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 prodnet) do the following:
* <ol>
* <li>Backup your current wallet.dat in case of unforeseen problems</li>
* <li>Start your litecoin client in test mode <code>litecoin -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.freelitecoins.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 litecoin 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/litecoin/files/Litecoin/testnet-in-a-box/">testnet in a box</a> to do everything purely locally.</p>
*/
public class PingService {
private final PeerGroup peerGroup;
private final BlockChain chain;
private final BlockStore blockStore;
private final File walletFile;
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
new PingService(args);
}
public PingService(String[] args) throws Exception {
boolean testNet = args.length > 0 && args[0].equalsIgnoreCase("testnet");
final NetworkParameters params = testNet ? NetworkParameters.testNet() : NetworkParameters.prodNet();
String filePrefix = testNet ? "pingservice-testnet" : "pingservice-prodnet";
// Try to read the wallet from storage, create a new one if not possible.
walletFile = new File(filePrefix + ".wallet");
Wallet w;
try {
w = Wallet.loadFromFile(walletFile);
} catch (IOException e) {
w = new Wallet(params);
w.keychain.add(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");
File file = new File(filePrefix + ".spvchain");
boolean chainExistedAlready = file.exists();
blockStore = new SPVBlockStore(params, file);
if (!chainExistedAlready) {
File checkpointsFile = new File("checkpoints");
if (checkpointsFile.exists()) {
FileInputStream stream = new FileInputStream(checkpointsFile);
CheckpointManager.checkpoint(params, stream, blockStore, key.getCreationTimeSeconds());
}
}
// Connect to the localhost node. One minute timeout since we won't try any other peers
System.out.println("Connecting ...");
chain = new BlockChain(params, wallet, blockStore);
peerGroup = new PeerGroup(params, chain);
peerGroup.setUserAgent("PingService", "1.0");
if (testNet) {
peerGroup.addPeerDiscovery(new IrcDiscovery("#litecoinTEST3"));
} 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) {
// MUST BE THREAD SAFE
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.litecoinValueToFriendlyString(value) +
": " + tx);
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(Transaction tx2) {
// Must be thread safe.
if (tx2.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
// Coins were confirmed (appeared in a block).
tx2.getConfidence().removeEventListener(this);
bounceCoins(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 bounceCoins(final Wallet wallet, Transaction tx) {
// It's impossible to pick one specific identity that you receive coins from in Litecoin as there
// could be inputs from many addresses. So instead we just pick the first and assume they were all
// owned by the same person.
try {
BigInteger value = tx.getValueSentToMe(wallet);
TransactionInput input = tx.getInputs().get(0);
Address from = input.getFromAddress();
System.out.println("Received " + Utils.litecoinValueToFriendlyString(value) + " from " + from.toString());
// Now send the coins back!
final Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, from, value);
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());
try {
wallet.saveToFile(walletFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void onFailure(Throwable throwable) {
System.err.println("Failed to send coins :(");
throwable.printStackTrace();
}
});
} catch (ScriptException e) {
// If we didn't understand the scriptSig, just crash.
throw new RuntimeException(e);
} catch (KeyCrypterException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}