3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 07:12:17 +00:00

WalletAppKit: support for restoring a wallet from a seed. The old wallet is moved out of the way.

This commit is contained in:
Mike Hearn 2014-07-11 00:26:37 +02:00
parent 68bb476430
commit 78383f98f4

View File

@ -22,6 +22,7 @@ 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.bitcoin.wallet.DeterministicSeed;
import com.google.bitcoin.wallet.KeyChainGroup;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AbstractIdleService;
@ -29,7 +30,10 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Service;
import com.subgraph.orchid.TorClient;
import org.bitcoinj.wallet.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
@ -64,6 +68,8 @@ import static com.google.common.base.Preconditions.checkState;
* out what went wrong more precisely. Same thing if you just use the {@link #startAsync()} method.</p>
*/
public class WalletAppKit extends AbstractIdleService {
protected static final Logger log = LoggerFactory.getLogger(WalletAppKit.class);
protected final String filePrefix;
protected final NetworkParameters params;
protected volatile BlockChain vChain;
@ -83,6 +89,7 @@ public class WalletAppKit extends AbstractIdleService {
protected boolean useTor = false; // Perhaps in future we can change this to true.
protected String userAgent, version;
protected WalletProtobufSerializer.WalletFactory walletFactory;
@Nullable protected DeterministicSeed restoreFromSeed;
public WalletAppKit(NetworkParameters params, File directory, String filePrefix) {
this.params = checkNotNull(params);
@ -170,6 +177,19 @@ public class WalletAppKit extends AbstractIdleService {
return this;
}
/**
* If a seed is set here then any existing wallet that matches the file name will be renamed to a backup name,
* the chain file will be deleted, and the wallet object will be instantiated with the given seed instead of
* a fresh one being created. This is intended for restoring a wallet from the original seed. To implement restore
* you would shut down the existing appkit, if any, then recreate it with the seed given by the user, then start
* up the new kit. The next time your app starts it should work as normal (that is, don't keep calling this each
* time).
*/
public WalletAppKit restoreWalletFromSeed(DeterministicSeed seed) {
this.restoreFromSeed = seed;
return this;
}
/**
* <p>Override this to return wallet extensions if any are necessary.</p>
*
@ -217,24 +237,36 @@ public class WalletAppKit extends AbstractIdleService {
throw new IOException("Could not create named directory.");
}
}
log.info("Starting up with directory = {}", directory);
try {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
vWalletFile = new File(directory, filePrefix + ".wallet");
boolean shouldReplayWallet = vWalletFile.exists() && !chainFileExists;
boolean shouldReplayWallet = (vWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
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.
if ((!chainFileExists || restoreFromSeed != null) && checkpoints != null) {
// Initialize the chain file with a checkpoint to speed up first-run sync.
long time = Long.MAX_VALUE;
if (vWalletFile.exists()) {
FileInputStream stream = new FileInputStream(vWalletFile);
final WalletProtobufSerializer serializer = new WalletProtobufSerializer();
final Wallet wallet = serializer.readWallet(params, null, WalletProtobufSerializer.parseToProto(stream));
time = wallet.getEarliestKeyCreationTime();
if (restoreFromSeed != null) {
time = restoreFromSeed.getCreationTimeSeconds();
if (chainFileExists) {
log.info("Deleting the chain file in preparation from restore.");
vStore.close();
if (!chainFile.delete())
throw new Exception("Failed to delete chain file in preparation for restore.");
vStore = new SPVBlockStore(params, chainFile);
}
} else {
// 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.
if (vWalletFile.exists()) {
FileInputStream stream = new FileInputStream(vWalletFile);
final WalletProtobufSerializer serializer = new WalletProtobufSerializer();
final Wallet wallet = serializer.readWallet(params, null, WalletProtobufSerializer.parseToProto(stream));
time = wallet.getEarliestKeyCreationTime();
}
}
CheckpointManager.checkpoint(params, checkpoints, vStore, time);
}
@ -242,6 +274,9 @@ public class WalletAppKit extends AbstractIdleService {
vPeerGroup = createPeerGroup();
if (this.userAgent != null)
vPeerGroup.setUserAgent(userAgent, version);
maybeMoveOldWalletOutOfTheWay();
if (vWalletFile.exists()) {
FileInputStream walletStream = new FileInputStream(vWalletFile);
try {
@ -261,7 +296,7 @@ public class WalletAppKit extends AbstractIdleService {
walletStream.close();
}
} else {
vWallet = walletFactory != null ? walletFactory.create(params, new KeyChainGroup(params)) : new Wallet(params);
vWallet = createWallet();
vWallet.freshReceiveKey();
for (WalletExtension e : provideWalletExtensions()) {
vWallet.addExtension(e);
@ -312,6 +347,35 @@ public class WalletAppKit extends AbstractIdleService {
}
}
protected Wallet createWallet() {
KeyChainGroup kcg;
if (restoreFromSeed != null)
kcg = new KeyChainGroup(params, restoreFromSeed);
else
kcg = new KeyChainGroup(params);
if (walletFactory != null) {
return walletFactory.create(params, kcg);
} else {
return new Wallet(params, kcg); // default
}
}
private void maybeMoveOldWalletOutOfTheWay() {
if (restoreFromSeed == null) return;
if (!vWalletFile.exists()) return;
int counter = 1;
File newName;
do {
newName = new File(vWalletFile.getParent(), "Backup " + counter + " for " + vWalletFile.getName());
counter++;
} while (newName.exists());
log.info("Renaming old wallet file {} to {}", vWalletFile, newName);
if (!vWalletFile.renameTo(newName)) {
// This should not happen unless something is really messed up.
throw new RuntimeException("Failed to rename wallet for restore");
}
}
protected PeerGroup createPeerGroup() throws TimeoutException {
if (useTor) {
return PeerGroup.newWithTor(params, vChain, new TorClient());