Use the standard Maven directory layout, rename "lib" to "core". Mavenize submodules.

This commit is contained in:
Mike Hearn
2012-03-13 18:57:03 +01:00
parent 216deb2d35
commit 0e52c98c45
119 changed files with 348 additions and 37 deletions

View File

@@ -0,0 +1,124 @@
/**
* 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.store.BlockStoreException;
import com.google.bitcoin.store.DerbyBlockStore;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
/**
* 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.
*/
public class DerbyPingService {
public static void main(String[] args) throws Exception {
boolean testNet = args.length > 0 && args[0].equalsIgnoreCase("testnet");
final NetworkParameters params = testNet ? NetworkParameters.testNet() : NetworkParameters.prodNet();
String suffix = testNet ? "testnet" : "prodnet";
String filePrefix = "pingservice-" + suffix;
// Try to read the wallet from storage, create a new one if not possible.
Wallet wallet;
final File walletFile = new File(filePrefix + ".wallet");
try {
wallet = Wallet.loadFromFile(walletFile);
} catch (IOException e) {
wallet = new Wallet(params);
wallet.keychain.add(new ECKey());
wallet.saveToFile(walletFile);
}
// Fetch the first key in the wallet (should be the only key).
ECKey key = wallet.keychain.get(0);
// Load the block chain, if there is one stored locally.
System.out.println("Reading block store from disk");
long time = System.currentTimeMillis();
DerbyBlockStore blockStore = new DerbyBlockStore(params, ".bitcoinj-" + suffix);
System.out.println("Opened block store in " + (System.currentTimeMillis() - time) + " ms");
//iterateAll(blockStore);
//blockStore.close();
//System.exit(1);
// Connect to the localhost node. One minute timeout since we won't try any other peers
System.out.println("Connecting ...");
BlockChain chain = new BlockChain(params, wallet, blockStore);
final PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addAddress(new PeerAddress(InetAddress.getLocalHost()));
peerGroup.addWallet(wallet);
peerGroup.start();
// We want to know when the balance changes.
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
// Running on a peer thread.
assert !newBalance.equals(BigInteger.ZERO);
// It's impossible to pick one specific identity that you receive coins from in BitCoin 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 {
TransactionInput input = tx.getInputs().get(0);
Address from = input.getFromAddress();
BigInteger value = tx.getValueSentToMe(w);
System.out.println("Received " + Utils.bitcoinValueToFriendlyString(value) + " from " + from.toString());
// Now send the coins back!
Transaction sendTx = w.sendCoins(peerGroup, from, value);
assert sendTx != null; // We should never try to send more coins than we have!
System.out.println("Sent coins back! Transaction hash is " + sendTx.getHashAsString());
w.saveToFile(walletFile);
} catch (ScriptException e) {
// If we didn't understand the scriptSig, just crash.
e.printStackTrace();
throw new RuntimeException(e);
} catch (IOException e) {
e.printStackTrace();
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.");
// The peer thread keeps us alive until something kills the process.
}
/**
* @param blockStore
* @throws BlockStoreException
*/
static void iterateAll(DerbyBlockStore blockStore) throws BlockStoreException {
long time = System.currentTimeMillis();
StoredBlock block = blockStore.getChainHead();
int count = 0;
while (block != null) {
count++;
if (count % 1000 == 0)
System.out.println("iterated " + count);
block = block.getPrev(blockStore);
}
System.out.println("iterated " + count);
System.out.println("Iterated block store in " + (System.currentTimeMillis() - time) + " ms");
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.Wallet;
import java.io.File;
/**
* DumpWallet loads a serialized wallet and prints information about what it contains.
*/
public class DumpWallet {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("Usage: java DumpWallet <filename>");
return;
}
Wallet wallet = Wallet.loadFromFile(new File(args[0]));
System.out.println(wallet.toString());
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.store.BlockStore;
import com.google.bitcoin.store.MemoryBlockStore;
import java.net.InetAddress;
import java.util.concurrent.Future;
/**
* Downloads the block given a block hash from the localhost node and prints it out.
*/
public class FetchBlock {
public static void main(String[] args) throws Exception {
System.out.println("Connecting to node");
final NetworkParameters params = NetworkParameters.prodNet();
BlockStore blockStore = new MemoryBlockStore(params);
BlockChain chain = new BlockChain(params, blockStore);
final Peer peer = new Peer(params, new PeerAddress(InetAddress.getLocalHost()), chain);
peer.connect();
new Thread(new Runnable() {
public void run() {
try {
peer.run();
} catch (PeerException e) {
throw new RuntimeException(e);
}
}
}).start();
Sha256Hash blockHash = new Sha256Hash(args[0]);
Future<Block> future = peer.getBlock(blockHash);
System.out.println("Waiting for node to send us the requested block: " + blockHash);
Block block = future.get();
System.out.println(block);
peer.disconnect();
}
}

View File

@@ -0,0 +1,209 @@
/**
* 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.discovery.DnsDiscovery;
import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.BoundedOverheadBlockStore;
import com.google.bitcoin.utils.BriefLogFormatter;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.Date;
import java.util.Set;
/**
* <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 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 Wallet w;
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 {
String peerHost = args.length > 0 ? args[0] : null;
int peerPort = args.length > 1 ? Integer.parseInt(args[1]) : NetworkParameters.prodNet().port;
boolean testNet = peerPort != NetworkParameters.prodNet().port;
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");
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.keychain.get(0);
System.out.println(wallet);
// Load the block chain, if there is one stored locally.
System.out.println("Reading block store from disk");
blockStore = new BoundedOverheadBlockStore(params, new File(filePrefix + ".blockchain"));
// 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);
// Set some version info.
peerGroup.setUserAgent("PingService", "1.0");
// Download headers only until a day ago.
peerGroup.setFastCatchupTimeSecs((new Date().getTime() / 1000) - (60 * 60 * 24));
if (peerHost != null) {
peerGroup.addAddress(new PeerAddress(InetAddress.getByName(peerHost), peerPort));
} else {
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
}
peerGroup.addWallet(wallet);
peerGroup.start();
peerGroup.addEventListener(new AbstractPeerEventListener() {
@Override
public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) {
super.onBlocksDownloaded(peer, block, blocksLeft);
// Don't bother printing during block chain downloads.
if (blocksLeft > 0)
return;
Set<Transaction> transactions = wallet.getTransactions(false, false);
if (transactions.size() == 0) return;
System.out.println("Confidences of wallet transactions:");
for (Transaction tx : transactions) {
System.out.println(tx);
try {
System.out.println(tx.getConfidence());
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
System.out.println("Work done: " + tx.getConfidence().getWorkDone(chain).toString());
} catch (BlockStoreException e) {
throw new RuntimeException(e);
}
System.out.println();
}
}
});
// We want to know when the balance changes.
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
// Running on a peer thread.
assert !newBalance.equals(BigInteger.ZERO);
if (tx.isPending()) {
// 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);
System.out.println(tx.getConfidence());
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(Transaction tx2) {
if (tx2.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
// Coins were confirmed.
bounceCoins(tx2);
tx2.getConfidence().removeEventListener(this);
} else {
System.out.println(String.format("Confidence of %s changed, is now: %s",
tx2.getHashAsString(), tx2.getConfidence().toString()));
}
}
});
try {
w.saveToFile(walletFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
// Ignore for now, as we won't be allowed to spend until the tx is no longer pending. This is
// something that should be fixed in future.
return;
} else {
// We found the coins in a block directly, without it being broadcast first (catching up with
// the chain), so just send them right back immediately.
bounceCoins(tx);
}
}
});
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.");
// The PeerGroup thread keeps us alive until something kills the process.
}
private void bounceCoins(Transaction tx) {
// It's impossible to pick one specific identity that you receive coins from in BitCoin 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(w);
TransactionInput input = tx.getInputs().get(0);
Address from = input.getFromAddress();
System.out.println("Received " + Utils.bitcoinValueToFriendlyString(value) + " from " + from.toString());
// Now send the coins back!
Transaction sendTx = w.sendCoins(peerGroup, from, value);
assert sendTx != null; // We should never try to send more coins than we have!
System.out.println("Sent coins back! Transaction hash is " + sendTx.getHashAsString());
w.saveToFile(walletFile);
} catch (ScriptException e) {
// If we didn't understand the scriptSig, just crash.
e.printStackTrace();
throw new RuntimeException(e);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright 2011 John Sample.
*
* 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.NetworkConnection;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.TCPNetworkConnection;
import com.google.bitcoin.discovery.DnsDiscovery;
import com.google.bitcoin.discovery.IrcDiscovery;
import com.google.bitcoin.discovery.PeerDiscoveryException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Prints a list of IP addresses connected to the rendezvous point on the LFnet IRC channel.
*/
public class PrintPeers {
private static InetSocketAddress[] dnsPeers, ircPeers;
private static void printElapsed(long start) {
long now = System.currentTimeMillis();
System.out.println(String.format("Took %.2f seconds", (now - start) / 1000.0));
}
private static void printPeers(InetSocketAddress[] addresses) {
for (InetSocketAddress address : addresses) {
String hostAddress = address.getAddress().getHostAddress();
System.out.println(String.format("%s:%d", hostAddress.toString(), address.getPort()));
}
}
private static void printIRC() throws PeerDiscoveryException {
long start = System.currentTimeMillis();
IrcDiscovery d = new IrcDiscovery("#bitcoin") {
@Override
protected void onIRCReceive(String message) {
System.out.println("<- " + message);
}
@Override
protected void onIRCSend(String message) {
System.out.println("-> " + message);
}
};
ircPeers = d.getPeers();
printPeers(ircPeers);
printElapsed(start);
}
private static void printDNS() throws PeerDiscoveryException {
long start = System.currentTimeMillis();
DnsDiscovery dns = new DnsDiscovery(NetworkParameters.prodNet());
dnsPeers = dns.getPeers();
printPeers(dnsPeers);
printElapsed(start);
}
public static void main(String[] args) throws Exception {
System.out.println("=== IRC ===");
printIRC();
System.out.println("=== DNS ===");
printDNS();
System.out.println("=== Version/chain heights ===");
ExecutorService pool = Executors.newFixedThreadPool(100);
ArrayList<InetAddress> addrs = new ArrayList<InetAddress>();
for (InetSocketAddress peer : dnsPeers) addrs.add(peer.getAddress());
for (InetSocketAddress peer : ircPeers) addrs.add(peer.getAddress());
System.out.println("Scanning " + addrs.size() + " peers:");
final Object lock = new Object();
final long[] bestHeight = new long[1];
for (final InetAddress addr : addrs) {
pool.submit(new Runnable() {
public void run() {
try {
NetworkConnection conn = new TCPNetworkConnection(addr,
NetworkParameters.prodNet(), 0, 1000);
synchronized (lock) {
long nodeHeight = conn.getVersionMessage().bestHeight;
long diff = bestHeight[0] - nodeHeight;
if (diff > 0) {
System.out.println("Node is behind by " + diff + " blocks: " + addr.toString());
} else {
bestHeight[0] = nodeHeight;
}
}
conn.shutdown();
} catch (Exception e) {
}
}
});
}
pool.awaitTermination(3600 * 24, TimeUnit.SECONDS); // 1 Day
}
}

View File

@@ -0,0 +1,79 @@
/**
* 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.store.MemoryBlockStore;
import java.math.BigInteger;
import java.net.InetAddress;
/**
* This example shows how to solve the challenge Hal posted here:<p>
*
* <a href="http://www.bitcoin.org/smf/index.php?topic=3638.0">http://www.bitcoin.org/smf/index.php?topic=3638
* .0</a><p>
*
* in which a private key with some coins associated with it is published. The goal is to import the private key,
* claim the coins and then send them to a different address.
*/
public class PrivateKeys {
public static void main(String[] args) throws Exception {
// TODO: Assumes production network not testnet. Make it selectable.
NetworkParameters params = NetworkParameters.prodNet();
try {
// Decode the private key from Satoshis Base58 variant. If 51 characters long then it's from Bitcoins
// dumpprivkey command and includes a version byte and checksum. Otherwise assume it's a raw key.
ECKey key;
if (args[0].length() == 51) {
DumpedPrivateKey dumpedPrivateKey = new DumpedPrivateKey(params, args[0]);
key = dumpedPrivateKey.getKey();
} else {
BigInteger privKey = Base58.decodeToBigInteger(args[0]);
key = new ECKey(privKey);
}
System.out.println("Address from private key is: " + key.toAddress(params).toString());
// And the address ...
Address destination = new Address(params, args[1]);
// Import the private key to a fresh wallet.
Wallet wallet = new Wallet(params);
wallet.addKey(key);
// Find the transactions that involve those coins.
final MemoryBlockStore blockStore = new MemoryBlockStore(params);
BlockChain chain = new BlockChain(params, wallet, blockStore);
final PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addAddress(new PeerAddress(InetAddress.getLocalHost()));
peerGroup.start();
peerGroup.downloadBlockChain();
peerGroup.stop();
// And take them!
System.out.println("Claiming " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()) + " coins");
wallet.sendCoins(peerGroup, destination, wallet.getBalance());
// Wait a few seconds to let the packets flush out to the network (ugly).
Thread.sleep(5000);
System.exit(0);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("First arg should be private key in Base58 format. Second argument should be address " +
"to send to.");
return;
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.store.BlockStore;
import com.google.bitcoin.store.MemoryBlockStore;
import java.io.File;
import java.math.BigInteger;
import java.net.InetAddress;
/**
* RefreshWallet loads a wallet, then processes the block chain to update the transaction pools within it.
*/
public class RefreshWallet {
public static void main(String[] args) throws Exception {
File file = new File(args[0]);
Wallet wallet = Wallet.loadFromFile(file);
System.out.println(wallet.toString());
// Set up the components and link them together.
final NetworkParameters params = NetworkParameters.testNet();
BlockStore blockStore = new MemoryBlockStore(params);
BlockChain chain = new BlockChain(params, wallet, blockStore);
final PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addAddress(new PeerAddress(InetAddress.getLocalHost()));
peerGroup.start();
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
System.out.println("\nReceived tx " + tx.getHashAsString());
System.out.println(tx.toString());
}
});
// Now download and process the block chain.
peerGroup.downloadBlockChain();
peerGroup.stop();
wallet.saveToFile(file);
System.out.println("\nDone!\n");
System.out.println(wallet.toString());
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.toywallet;
import com.google.bitcoin.core.*;
import com.google.bitcoin.discovery.DnsDiscovery;
import com.google.bitcoin.store.BoundedOverheadBlockStore;
import com.google.bitcoin.utils.BriefLogFormatter;
import org.bouncycastle.util.encoders.Hex;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
* A GUI demo that lets you watch received transactions as they accumulate confidence.
*/
public class ToyWallet {
private final TxListModel txListModel = new TxListModel();
private JList txList;
private NetworkParameters params;
private Wallet wallet;
private PeerGroup peerGroup;
private BlockChain chain;
private JLabel networkStats;
private File walletFile;
private JScrollPane txScrollPane;
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
new ToyWallet(false, args);
}
public ToyWallet(boolean testnet, String[] args) throws Exception {
// Set up a Bitcoin connection + empty wallet. TODO: Simplify the setup for this use case.
if (testnet) {
params = NetworkParameters.testNet();
} else {
params = NetworkParameters.prodNet();
}
// Try to read the wallet from storage, create a new one if not possible.
boolean freshWallet = false;
walletFile = new File("toy.wallet");
try {
wallet = Wallet.loadFromFile(walletFile);
} catch (IOException e) {
wallet = new Wallet(params);
// Allow user to specify the first key on the command line as:
// hex-encoded-key:creation-time-seconds
ECKey key;
if (args.length > 0) {
try {
String[] parts = args[0].split(":");
byte[] pubKey = Hex.decode(parts[0]);
key = new ECKey(null, pubKey);
long creationTimeSeconds = Long.parseLong(parts[1]);
key.setCreationTimeSeconds(creationTimeSeconds);
System.out.println(String.format("Using address from command line %s, created on %s",
key.toAddress(params).toString(), new Date(creationTimeSeconds*1000).toString()));
} catch (Exception e2) {
System.err.println("Could not understand argument. Try a hex encoded pub key with a creation " +
"time in seconds appended with a colon in between: " + e2.toString());
return;
}
} else {
key = new ECKey(); // Generate a fresh key.
}
wallet.keychain.add(key);
wallet.saveToFile(walletFile);
freshWallet = true;
}
System.out.println("Send to: " + wallet.keychain.get(0).toAddress(params));
System.out.println(wallet);
File blockChainFile = new File("toy.blockchain");
if (!blockChainFile.exists() && !freshWallet) {
// No block chain, but we had a wallet. So empty out the transactions in the wallet so when we rescan
// the blocks there are no problems (wallets don't support replays without being emptied).
wallet.clearTransactions(0);
}
chain = new BlockChain(params, wallet, new BoundedOverheadBlockStore(params, blockChainFile));
peerGroup = new PeerGroup(params, chain);
peerGroup.setUserAgent("ToyWallet", "1.0");
if (testnet) {
peerGroup.addAddress(new PeerAddress(InetAddress.getByName("plan99.net"), 18333));
peerGroup.addAddress(new PeerAddress(InetAddress.getByName("localhost"), 18333));
} else {
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
}
peerGroup.addWallet(wallet);
peerGroup.setFastCatchupTimeSecs(wallet.getEarliestKeyCreationTime());
// Watch for peers coming and going so we can update the UI.
peerGroup.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
super.onPeerConnected(peer, peerCount);
triggerNetworkStatsUpdate();
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
super.onPeerDisconnected(peer, peerCount);
triggerNetworkStatsUpdate();
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) {
super.onBlocksDownloaded(peer, block, blocksLeft);
triggerNetworkStatsUpdate();
handleNewBlock();
}
});
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
super.onCoinsReceived(wallet, tx, prevBalance, newBalance);
handleNewTransaction(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
super.onCoinsSent(wallet, tx, prevBalance, newBalance);
handleNewTransaction(tx);
}
@Override
public void onChange() {
try {
System.out.println("Wallet changed");
wallet.saveToFile(walletFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
// Create the GUI.
JFrame window = new JFrame("Toy wallet");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setupWindow(window);
window.pack();
window.setSize(640, 480);
// Put the transactions stored in the wallet, into the GUI.
final Set<Transaction> walletTransactions = wallet.getTransactions(true, true);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
for (final Transaction tx : walletTransactions) {
txListModel.monitorTx(tx);
}
}
});
// Go!
window.setVisible(true);
peerGroup.start();
peerGroup.downloadBlockChain();
}
private void handleNewBlock() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
txListModel.newBlock();
}
});
}
private void handleNewTransaction(final Transaction t) {
// Running on a peer thread, switch to Swing thread before adding and updating the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
txListModel.monitorTx(t);
}
});
}
private void triggerNetworkStatsUpdate() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int numPeers = peerGroup.numConnectedPeers();
StoredBlock chainHead = chain.getChainHead();
String date = chainHead.getHeader().getTime().toString();
String plural = numPeers > 1 ? "peers" : "peer";
String status = String.format("%d %s connected. %d blocks: %s",
numPeers, plural, chainHead.getHeight(), date);
networkStats.setText(status);
}
});
}
private void setupWindow(JFrame window) {
JLabel instructions = new JLabel(
"<html>Broadcast transactions appear below. Watch them gain confidence.<br>" +
"Send coins to: <b>" + wallet.keychain.get(0).toAddress(params) + "</b>");
window.getContentPane().add(instructions, BorderLayout.NORTH);
// The list of transactions.
txList = new JList(txListModel);
txList.setCellRenderer(new TxListLabel());
txList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
txList.setLayoutOrientation(JList.VERTICAL);
txScrollPane = new JScrollPane(txList);
window.getContentPane().add(txScrollPane, BorderLayout.CENTER);
networkStats = new JLabel("Connecting to the Bitcoin network ...");
window.getContentPane().add(networkStats, BorderLayout.SOUTH);
}
// Object that manages the contents of the list view.
private class TxListModel extends AbstractListModel {
private List<Transaction> transactions = new ArrayList<Transaction>();
public void monitorTx(Transaction tx) {
assert SwingUtilities.isEventDispatchThread();
transactions.add(tx);
// Set up a tx confidence change event listener, so we know when to update the list.
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(Transaction tx) {
// Note that this does NOT get called for every block that is received, just when we transition
// between confidence states.
int txIndex = transactions.indexOf(tx);
fireContentsChanged(this, txIndex, txIndex);
}
});
fireIntervalAdded(this, transactions.size() - 1, transactions.size() - 1);
}
public int getSize() {
return transactions.size();
}
public Object getElementAt(int i) {
Transaction tx = transactions.get(i);
return tx.toString() + "\n" + tx.getConfidence().toString();
}
public void newBlock() {
fireContentsChanged(this, 0, getSize() - 1);
}
}
private class TxListLabel extends JLabel implements ListCellRenderer {
public Component getListCellRendererComponent(JList list, Object contents,
int index, boolean isSelected,
boolean cellHasFocus) {
String value = (String) contents;
final String key = wallet.keychain.get(0).toAddress(params).toString();
value = "<html>" + value.replaceAll("\n", "<br>").replaceAll("<br> ", "<br>&nbsp;&nbsp;")
.replaceAll(key, "<i>" + key + "</i>");
setText(value);
setOpaque(true);
setBackground(index % 2 == 1 ? new Color(230, 230, 230) : Color.WHITE);
return this;
}
}
}