mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 14:54:15 +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.BlockStoreException;
|
||||||
import com.google.bitcoin.store.SPVBlockStore;
|
import com.google.bitcoin.store.SPVBlockStore;
|
||||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||||
|
import com.google.bitcoin.wallet.DeterministicSeed;
|
||||||
import com.google.bitcoin.wallet.KeyChainGroup;
|
import com.google.bitcoin.wallet.KeyChainGroup;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.AbstractIdleService;
|
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.google.common.util.concurrent.Service;
|
||||||
import com.subgraph.orchid.TorClient;
|
import com.subgraph.orchid.TorClient;
|
||||||
import org.bitcoinj.wallet.Protos;
|
import org.bitcoinj.wallet.Protos;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
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>
|
* out what went wrong more precisely. Same thing if you just use the {@link #startAsync()} method.</p>
|
||||||
*/
|
*/
|
||||||
public class WalletAppKit extends AbstractIdleService {
|
public class WalletAppKit extends AbstractIdleService {
|
||||||
|
protected static final Logger log = LoggerFactory.getLogger(WalletAppKit.class);
|
||||||
|
|
||||||
protected final String filePrefix;
|
protected final String filePrefix;
|
||||||
protected final NetworkParameters params;
|
protected final NetworkParameters params;
|
||||||
protected volatile BlockChain vChain;
|
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 boolean useTor = false; // Perhaps in future we can change this to true.
|
||||||
protected String userAgent, version;
|
protected String userAgent, version;
|
||||||
protected WalletProtobufSerializer.WalletFactory walletFactory;
|
protected WalletProtobufSerializer.WalletFactory walletFactory;
|
||||||
|
@Nullable protected DeterministicSeed restoreFromSeed;
|
||||||
|
|
||||||
public WalletAppKit(NetworkParameters params, File directory, String filePrefix) {
|
public WalletAppKit(NetworkParameters params, File directory, String filePrefix) {
|
||||||
this.params = checkNotNull(params);
|
this.params = checkNotNull(params);
|
||||||
@ -170,6 +177,19 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
return this;
|
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>
|
* <p>Override this to return wallet extensions if any are necessary.</p>
|
||||||
*
|
*
|
||||||
@ -217,31 +237,46 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
throw new IOException("Could not create named directory.");
|
throw new IOException("Could not create named directory.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.info("Starting up with directory = {}", directory);
|
||||||
try {
|
try {
|
||||||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||||
boolean chainFileExists = chainFile.exists();
|
boolean chainFileExists = chainFile.exists();
|
||||||
vWalletFile = new File(directory, filePrefix + ".wallet");
|
vWalletFile = new File(directory, filePrefix + ".wallet");
|
||||||
boolean shouldReplayWallet = vWalletFile.exists() && !chainFileExists;
|
boolean shouldReplayWallet = (vWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||||
|
|
||||||
vStore = new SPVBlockStore(params, chainFile);
|
vStore = new SPVBlockStore(params, chainFile);
|
||||||
if (!chainFileExists && checkpoints != null) {
|
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 (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
|
// 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
|
// 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
|
// create the wallet later, but we need to know the time early here before we create the BlockChain
|
||||||
// object.
|
// object.
|
||||||
long time = Long.MAX_VALUE;
|
|
||||||
if (vWalletFile.exists()) {
|
if (vWalletFile.exists()) {
|
||||||
FileInputStream stream = new FileInputStream(vWalletFile);
|
FileInputStream stream = new FileInputStream(vWalletFile);
|
||||||
final WalletProtobufSerializer serializer = new WalletProtobufSerializer();
|
final WalletProtobufSerializer serializer = new WalletProtobufSerializer();
|
||||||
final Wallet wallet = serializer.readWallet(params, null, WalletProtobufSerializer.parseToProto(stream));
|
final Wallet wallet = serializer.readWallet(params, null, WalletProtobufSerializer.parseToProto(stream));
|
||||||
time = wallet.getEarliestKeyCreationTime();
|
time = wallet.getEarliestKeyCreationTime();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CheckpointManager.checkpoint(params, checkpoints, vStore, time);
|
CheckpointManager.checkpoint(params, checkpoints, vStore, time);
|
||||||
}
|
}
|
||||||
vChain = new BlockChain(params, vStore);
|
vChain = new BlockChain(params, vStore);
|
||||||
vPeerGroup = createPeerGroup();
|
vPeerGroup = createPeerGroup();
|
||||||
if (this.userAgent != null)
|
if (this.userAgent != null)
|
||||||
vPeerGroup.setUserAgent(userAgent, version);
|
vPeerGroup.setUserAgent(userAgent, version);
|
||||||
|
|
||||||
|
maybeMoveOldWalletOutOfTheWay();
|
||||||
|
|
||||||
if (vWalletFile.exists()) {
|
if (vWalletFile.exists()) {
|
||||||
FileInputStream walletStream = new FileInputStream(vWalletFile);
|
FileInputStream walletStream = new FileInputStream(vWalletFile);
|
||||||
try {
|
try {
|
||||||
@ -261,7 +296,7 @@ public class WalletAppKit extends AbstractIdleService {
|
|||||||
walletStream.close();
|
walletStream.close();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vWallet = walletFactory != null ? walletFactory.create(params, new KeyChainGroup(params)) : new Wallet(params);
|
vWallet = createWallet();
|
||||||
vWallet.freshReceiveKey();
|
vWallet.freshReceiveKey();
|
||||||
for (WalletExtension e : provideWalletExtensions()) {
|
for (WalletExtension e : provideWalletExtensions()) {
|
||||||
vWallet.addExtension(e);
|
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 {
|
protected PeerGroup createPeerGroup() throws TimeoutException {
|
||||||
if (useTor) {
|
if (useTor) {
|
||||||
return PeerGroup.newWithTor(params, vChain, new TorClient());
|
return PeerGroup.newWithTor(params, vChain, new TorClient());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user