All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.bitcoin.kits.WalletAppKit Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 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.kits;

import com.google.bitcoin.core.*;
import com.google.bitcoin.net.discovery.DnsDiscovery;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.SPVBlockStore;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.common.util.concurrent.AbstractIdleService;
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.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * 

Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory * and file prefix, optionally configure a few things, then use start or startAndWait. The object will construct and * configure a {@link BlockChain}, {@link SPVBlockStore}, {@link Wallet} and {@link PeerGroup}. Depending on the value * of the blockingStartup property, startup will be considered complete once the block chain has fully synchronized, * so it can take a while.

* *

To add listeners and modify the objects that are constructed, you can either do that by overriding the * {@link #onSetupCompleted()} method (which will run on a background thread) and make your changes there, * or by waiting for the service to start and then accessing the objects from wherever you want. However, you cannot * access the objects this class creates until startup is complete.

* *

The asynchronous design of this class may seem puzzling (just use {@link #startAndWait()} if you don't want that). * It is to make it easier to fit bitcoinj into GUI apps, which require a high degree of responsiveness on their main * thread which handles all the animation and user interaction. Even when blockingStart is false, initializing bitcoinj * means doing potentially blocking file IO, generating keys and other potentially intensive operations. By running it * on a background thread, there's no risk of accidentally causing UI lag.

* *

Note that {@link #startAndWait()} can throw an unchecked {@link com.google.common.util.concurrent.UncheckedExecutionException} * if anything goes wrong during startup - you should probably handle it and use {@link Exception#getCause()} to figure * out what went wrong more precisely. Same thing if you use the async start() method.

*/ public class WalletAppKit extends AbstractIdleService { protected final String filePrefix; protected final NetworkParameters params; protected volatile BlockChain vChain; protected volatile SPVBlockStore vStore; protected volatile Wallet vWallet; protected volatile PeerGroup vPeerGroup; protected final File directory; protected volatile File vWalletFile; protected boolean useAutoSave = true; protected PeerAddress[] peerAddresses; protected PeerEventListener downloadListener; protected boolean autoStop = true; protected InputStream checkpoints; protected boolean blockingStartup = true; protected String userAgent, version; public WalletAppKit(NetworkParameters params, File directory, String filePrefix) { this.params = checkNotNull(params); this.directory = checkNotNull(directory); this.filePrefix = checkNotNull(filePrefix); } /** Will only connect to the given addresses. Cannot be called after startup. */ public WalletAppKit setPeerNodes(PeerAddress... addresses) { checkState(state() == State.NEW, "Cannot call after startup"); this.peerAddresses = addresses; return this; } /** Will only connect to localhost. Cannot be called after startup. */ public WalletAppKit connectToLocalHost() { try { final InetAddress localHost = InetAddress.getLocalHost(); return setPeerNodes(new PeerAddress(localHost, params.getPort())); } catch (UnknownHostException e) { // Borked machine with no loopback adapter configured properly. throw new RuntimeException(e); } } /** If true, the wallet will save itself to disk automatically whenever it changes. */ public WalletAppKit setAutoSave(boolean value) { checkState(state() == State.NEW, "Cannot call after startup"); useAutoSave = value; return this; } /** * If you want to learn about the sync process, you can provide a listener here. For instance, a * {@link DownloadListener} is a good choice. */ public WalletAppKit setDownloadListener(PeerEventListener listener) { this.downloadListener = listener; return this; } /** If true, will register a shutdown hook to stop the library. Defaults to true. */ public WalletAppKit setAutoStop(boolean autoStop) { this.autoStop = autoStop; return this; } /** * If set, the file is expected to contain a checkpoints file calculated with BuildCheckpoints. It makes initial * block sync faster for new users - please refer to the documentation on the bitcoinj website for further details. */ public WalletAppKit setCheckpoints(InputStream checkpoints) { this.checkpoints = checkNotNull(checkpoints); return this; } /** * If true (the default) then the startup of this service won't be considered complete until the network has been * brought up, peer connections established and the block chain synchronised. Therefore {@link #startAndWait()} can * potentially take a very long time. If false, then startup is considered complete once the network activity * begins and peer connections/block chain sync will continue in the background. */ public WalletAppKit setBlockingStartup(boolean blockingStartup) { this.blockingStartup = blockingStartup; return this; } /** * Sets the string that will appear in the subver field of the version message. * @param userAgent A short string that should be the name of your app, e.g. "My Wallet" * @param version A short string that contains the version number, e.g. "1.0-BETA" */ public WalletAppKit setUserAgent(String userAgent, String version) { this.userAgent = checkNotNull(userAgent); this.version = checkNotNull(version); return this; } /** *

Override this to load all wallet extensions if any are necessary.

* *

When this is called, chain(), store(), and peerGroup() will return the created objects, however they are not * initialized/started

*/ protected void addWalletExtensions() throws Exception { } /** * This method is invoked on a background thread after all objects are initialised, but before the peer group * or block chain download is started. You can tweak the objects configuration here. */ protected void onSetupCompleted() { } @Override protected void startUp() throws Exception { // Runs in a separate thread. if (!directory.exists()) { if (!directory.mkdir()) { throw new IOException("Could not create named directory."); } } try { File chainFile = new File(directory, filePrefix + ".spvchain"); boolean chainFileExists = chainFile.exists(); vWalletFile = new File(directory, filePrefix + ".wallet"); boolean shouldReplayWallet = vWalletFile.exists() && !chainFileExists; vStore = new SPVBlockStore(params, chainFile); if (!chainFileExists && checkpoints != null) { // Ugly hack! We have to create the wallet once here to learn the earliest key time, and then throw it // away. The reason is that wallet extensions might need access to peergroups/chains/etc so we have to // create the wallet later, but we need to know the time early here before we create the BlockChain // object. long time = Long.MAX_VALUE; if (vWalletFile.exists()) { Wallet wallet = new Wallet(params); FileInputStream stream = new FileInputStream(vWalletFile); new WalletProtobufSerializer().readWallet(WalletProtobufSerializer.parseToProto(stream), wallet); time = wallet.getEarliestKeyCreationTime(); } CheckpointManager.checkpoint(params, checkpoints, vStore, time); } vChain = new BlockChain(params, vStore); vPeerGroup = createPeerGroup(); if (this.userAgent != null) vPeerGroup.setUserAgent(userAgent, version); if (vWalletFile.exists()) { FileInputStream walletStream = new FileInputStream(vWalletFile); try { vWallet = new Wallet(params); addWalletExtensions(); // All extensions must be present before we deserialize new WalletProtobufSerializer().readWallet(WalletProtobufSerializer.parseToProto(walletStream), vWallet); if (shouldReplayWallet) vWallet.clearTransactions(0); } finally { walletStream.close(); } } else { vWallet = new Wallet(params); vWallet.addKey(new ECKey()); addWalletExtensions(); vWallet.saveToFile(vWalletFile); } if (useAutoSave) { vWallet.autosaveToFile(vWalletFile, 200, TimeUnit.MILLISECONDS, null); } // Set up peer addresses or discovery first, so if wallet extensions try to broadcast a transaction // before we're actually connected the broadcast waits for an appropriate number of connections. if (peerAddresses != null) { for (PeerAddress addr : peerAddresses) vPeerGroup.addAddress(addr); peerAddresses = null; } else { vPeerGroup.addPeerDiscovery(new DnsDiscovery(params)); } vChain.addWallet(vWallet); vPeerGroup.addWallet(vWallet); onSetupCompleted(); if (blockingStartup) { vPeerGroup.startAndWait(); // Make sure we shut down cleanly. installShutdownHook(); // TODO: Be able to use the provided download listener when doing a blocking startup. final DownloadListener listener = new DownloadListener(); vPeerGroup.startBlockChainDownload(listener); listener.await(); } else { Futures.addCallback(vPeerGroup.start(), new FutureCallback() { @Override public void onSuccess(State result) { final PeerEventListener l = downloadListener == null ? new DownloadListener() : downloadListener; vPeerGroup.startBlockChainDownload(l); } @Override public void onFailure(Throwable t) { throw new RuntimeException(t); } }); } } catch (BlockStoreException e) { throw new IOException(e); } } protected PeerGroup createPeerGroup() { return new PeerGroup(params, vChain); } private void installShutdownHook() { if (autoStop) Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { WalletAppKit.this.stopAndWait(); } catch (Exception e) { throw new RuntimeException(e); } } }); } @Override protected void shutDown() throws Exception { // Runs in a separate thread. try { vPeerGroup.stopAndWait(); vWallet.saveToFile(vWalletFile); vStore.close(); vPeerGroup = null; vWallet = null; vStore = null; vChain = null; } catch (BlockStoreException e) { throw new IOException(e); } } public NetworkParameters params() { return params; } public BlockChain chain() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); return vChain; } public SPVBlockStore store() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); return vStore; } public Wallet wallet() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); return vWallet; } public PeerGroup peerGroup() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); return vPeerGroup; } public File directory() { return directory; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy