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:
parent
68bb476430
commit
78383f98f4
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user