mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-09 05:14:46 +01:00
Introduce UnreadableWalletException and make WalletProtobufSerializer throw it in all cases where there's a problem reading the wallet. Resolves issue 415. Resolves issue 416.
This commit is contained in:
parent
be89be6f43
commit
99f32b16af
6 changed files with 84 additions and 71 deletions
|
@ -21,6 +21,7 @@ import com.google.bitcoin.core.WalletTransaction.Pool;
|
|||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.bitcoin.store.UnreadableWalletException;
|
||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||
import com.google.bitcoin.utils.ListenerRegistration;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
|
@ -511,12 +512,17 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||
/**
|
||||
* Returns a wallet deserialized from the given file.
|
||||
*/
|
||||
public static Wallet loadFromFile(File f) throws IOException {
|
||||
FileInputStream stream = new FileInputStream(f);
|
||||
public static Wallet loadFromFile(File f) throws UnreadableWalletException {
|
||||
try {
|
||||
return loadFromFileStream(stream);
|
||||
} finally {
|
||||
stream.close();
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
stream = new FileInputStream(f);
|
||||
return loadFromFileStream(stream);
|
||||
} finally {
|
||||
if (stream != null) stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnreadableWalletException("Could not open file", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,29 +574,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||
/**
|
||||
* Returns a wallet deserialized from the given input stream.
|
||||
*/
|
||||
public static Wallet loadFromFileStream(InputStream stream) throws IOException {
|
||||
// Determine what kind of wallet stream this is: Java Serialization or protobuf format.
|
||||
stream = new BufferedInputStream(stream);
|
||||
stream.mark(100);
|
||||
boolean serialization = stream.read() == 0xac && stream.read() == 0xed;
|
||||
stream.reset();
|
||||
|
||||
Wallet wallet;
|
||||
|
||||
if (serialization) {
|
||||
ObjectInputStream ois = null;
|
||||
try {
|
||||
ois = new ObjectInputStream(stream);
|
||||
wallet = (Wallet) ois.readObject();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (ois != null) ois.close();
|
||||
}
|
||||
} else {
|
||||
wallet = new WalletProtobufSerializer().readWallet(stream);
|
||||
}
|
||||
|
||||
public static Wallet loadFromFileStream(InputStream stream) throws UnreadableWalletException {
|
||||
Wallet wallet = new WalletProtobufSerializer().readWallet(stream);
|
||||
if (!wallet.isConsistent()) {
|
||||
log.error("Loaded an inconsistent wallet");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.google.bitcoin.store;
|
||||
|
||||
/**
|
||||
* Thrown by the {@link WalletProtobufSerializer} when the serialized protocol buffer is either corrupted,
|
||||
* internally inconsistent or appears to be from the future.
|
||||
*/
|
||||
public class UnreadableWalletException extends Exception {
|
||||
public UnreadableWalletException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public UnreadableWalletException(String s, Throwable t) {
|
||||
super(s, t);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,18 @@
|
|||
|
||||
package com.google.bitcoin.store;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
||||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.TextFormat;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -27,19 +39,6 @@ import java.util.HashMap;
|
|||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
||||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.TextFormat;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
|
@ -297,12 +296,23 @@ public class WalletProtobufSerializer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a wallet from the given stream, using the provided Wallet instance to load data into. This is primarily
|
||||
* <p>Parses a wallet from the given stream, using the provided Wallet instance to load data into. This is primarily
|
||||
* used when you want to register extensions. Data in the proto will be added into the wallet where applicable and
|
||||
* overwrite where not.
|
||||
* overwrite where not.</p>
|
||||
*
|
||||
* <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally
|
||||
* inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always
|
||||
* handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.</p>
|
||||
*
|
||||
* @throws UnreadableWalletException thrown in various error conditions (see description).
|
||||
*/
|
||||
public Wallet readWallet(InputStream input) throws IOException {
|
||||
Protos.Wallet walletProto = parseToProto(input);
|
||||
public Wallet readWallet(InputStream input) throws UnreadableWalletException {
|
||||
Protos.Wallet walletProto = null;
|
||||
try {
|
||||
walletProto = parseToProto(input);
|
||||
} catch (IOException e) {
|
||||
throw new UnreadableWalletException("Could not load wallet file", e);
|
||||
}
|
||||
|
||||
// System.out.println(TextFormat.printToString(walletProto));
|
||||
|
||||
|
@ -313,15 +323,17 @@ public class WalletProtobufSerializer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily
|
||||
* <p>Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily
|
||||
* useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object
|
||||
* may be in an indeterminate state and should be thrown away.
|
||||
* may be in an indeterminate state and should be thrown away.</p>
|
||||
*
|
||||
* @throws IOException if there is a problem reading the stream.
|
||||
* @throws IllegalArgumentException if the wallet is corrupt.
|
||||
* <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally
|
||||
* inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always
|
||||
* handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.</p>
|
||||
*
|
||||
* @throws UnreadableWalletException thrown in various error conditions (see description).
|
||||
*/
|
||||
public void readWallet(Protos.Wallet walletProto, Wallet wallet) throws IOException {
|
||||
// TODO: This method should throw more specific exception types than IllegalArgumentException.
|
||||
public void readWallet(Protos.Wallet walletProto, Wallet wallet) throws UnreadableWalletException {
|
||||
// Read the scrypt parameters that specify how encryption and decryption is performed.
|
||||
if (walletProto.hasEncryptionParameters()) {
|
||||
Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
|
||||
|
@ -335,7 +347,7 @@ public class WalletProtobufSerializer {
|
|||
// Read all keys
|
||||
for (Protos.Key keyProto : walletProto.getKeyList()) {
|
||||
if (!(keyProto.getType() == Protos.Key.Type.ORIGINAL || keyProto.getType() == Protos.Key.Type.ENCRYPTED_SCRYPT_AES)) {
|
||||
throw new IllegalArgumentException("Unknown key type in wallet, type = " + keyProto.getType());
|
||||
throw new UnreadableWalletException("Unknown key type in wallet, type = " + keyProto.getType());
|
||||
}
|
||||
|
||||
byte[] privKey = keyProto.hasPrivateKey() ? keyProto.getPrivateKey().toByteArray() : null;
|
||||
|
@ -394,14 +406,14 @@ public class WalletProtobufSerializer {
|
|||
txMap.clear();
|
||||
}
|
||||
|
||||
private static void loadExtensions(Wallet wallet, Protos.Wallet walletProto) {
|
||||
private static void loadExtensions(Wallet wallet, Protos.Wallet walletProto) throws UnreadableWalletException {
|
||||
final Map<String, WalletExtension> extensions = wallet.getExtensions();
|
||||
for (Protos.Extension extProto : walletProto.getExtensionList()) {
|
||||
String id = extProto.getId();
|
||||
WalletExtension extension = extensions.get(id);
|
||||
if (extension == null) {
|
||||
if (extProto.getMandatory()) {
|
||||
throw new IllegalArgumentException("Unknown mandatory extension in wallet: " + id);
|
||||
throw new UnreadableWalletException("Unknown mandatory extension in wallet: " + id);
|
||||
}
|
||||
} else {
|
||||
log.info("Loading wallet extension {}", id);
|
||||
|
@ -409,7 +421,7 @@ public class WalletProtobufSerializer {
|
|||
extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray());
|
||||
} catch (Exception e) {
|
||||
if (extProto.getMandatory())
|
||||
throw new IllegalArgumentException("Unknown mandatory extension in wallet: " + id);
|
||||
throw new UnreadableWalletException("Could not parse mandatory extension in wallet: " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,7 +436,7 @@ public class WalletProtobufSerializer {
|
|||
return Protos.Wallet.parseFrom(input);
|
||||
}
|
||||
|
||||
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
|
||||
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) throws UnreadableWalletException {
|
||||
Transaction tx = new Transaction(params);
|
||||
if (txProto.hasUpdatedAt()) {
|
||||
tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
|
||||
|
@ -460,14 +472,14 @@ public class WalletProtobufSerializer {
|
|||
|
||||
// Transaction should now be complete.
|
||||
Sha256Hash protoHash = byteStringToHash(txProto.getHash());
|
||||
Preconditions.checkState(tx.getHash().equals(protoHash),
|
||||
"Transaction did not deserialize completely: %s vs %s", tx.getHash(), protoHash);
|
||||
Preconditions.checkState(!txMap.containsKey(txProto.getHash()),
|
||||
"Wallet contained duplicate transaction %s", byteStringToHash(txProto.getHash()));
|
||||
if (!tx.getHash().equals(protoHash))
|
||||
throw new UnreadableWalletException(String.format("Transaction did not deserialize completely: %s vs %s", tx.getHash(), protoHash));
|
||||
if (txMap.containsKey(txProto.getHash()))
|
||||
throw new UnreadableWalletException("Wallet contained duplicate transaction " + byteStringToHash(txProto.getHash()));
|
||||
txMap.put(txProto.getHash(), tx);
|
||||
}
|
||||
|
||||
private WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) {
|
||||
private WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) throws UnreadableWalletException {
|
||||
Transaction tx = txMap.get(txProto.getHash());
|
||||
WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
|
||||
if (pool == WalletTransaction.Pool.INACTIVE || pool == WalletTransaction.Pool.PENDING_INACTIVE) {
|
||||
|
@ -483,9 +495,10 @@ public class WalletProtobufSerializer {
|
|||
if (transactionOutput.hasSpentByTransactionHash()) {
|
||||
final ByteString spentByTransactionHash = transactionOutput.getSpentByTransactionHash();
|
||||
Transaction spendingTx = txMap.get(spentByTransactionHash);
|
||||
if (spendingTx == null)
|
||||
throw new IllegalArgumentException(String.format("Could not connect %s to %s",
|
||||
if (spendingTx == null) {
|
||||
throw new UnreadableWalletException(String.format("Could not connect %s to %s",
|
||||
tx.getHashAsString(), byteStringToHash(spentByTransactionHash)));
|
||||
}
|
||||
final int spendingIndex = transactionOutput.getSpentByTransactionIndex();
|
||||
TransactionInput input = checkNotNull(spendingTx.getInput(spendingIndex));
|
||||
input.connect(output);
|
||||
|
@ -502,7 +515,7 @@ public class WalletProtobufSerializer {
|
|||
}
|
||||
|
||||
private void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto,
|
||||
TransactionConfidence confidence) {
|
||||
TransactionConfidence confidence) throws UnreadableWalletException {
|
||||
// We are lenient here because tx confidence is not an essential part of the wallet.
|
||||
// If the tx has an unknown type of confidence, ignore.
|
||||
if (!confidenceProto.hasType()) {
|
||||
|
@ -561,7 +574,7 @@ public class WalletProtobufSerializer {
|
|||
try {
|
||||
ip = InetAddress.getByAddress(proto.getIpAddress().toByteArray());
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e); // IP address is of invalid length.
|
||||
throw new UnreadableWalletException("Peer IP address does not have the right length", e);
|
||||
}
|
||||
int port = proto.getPort();
|
||||
PeerAddress address = new PeerAddress(ip, port);
|
||||
|
|
|
@ -275,8 +275,8 @@ public class WalletProtobufSerializerTest {
|
|||
try {
|
||||
new WalletProtobufSerializer().readWallet(proto, wallet2);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
} catch (UnreadableWalletException e) {
|
||||
assertTrue(e.getMessage().contains("mandatory"));
|
||||
}
|
||||
Wallet wallet3 = new Wallet(params);
|
||||
// This time it works.
|
||||
|
|
|
@ -24,13 +24,13 @@ import com.google.bitcoin.params.RegTestParams;
|
|||
import com.google.bitcoin.params.TestNet3Params;
|
||||
import com.google.bitcoin.store.BlockStore;
|
||||
import com.google.bitcoin.store.SPVBlockStore;
|
||||
import com.google.bitcoin.store.UnreadableWalletException;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
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.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
|
||||
|
@ -97,7 +97,7 @@ public class PingService {
|
|||
// Wipe the wallet if the chain file was deleted.
|
||||
if (walletFile.exists() && chainFile.exists())
|
||||
w = Wallet.loadFromFile(walletFile);
|
||||
} catch (IOException e) {
|
||||
} catch (UnreadableWalletException e) {
|
||||
System.err.println("Couldn't load wallet: " + e);
|
||||
// Fall through.
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.google.bitcoin.params.MainNetParams;
|
|||
import com.google.bitcoin.params.TestNet3Params;
|
||||
import com.google.bitcoin.store.H2FullPrunedBlockStore;
|
||||
import com.google.bitcoin.store.SPVBlockStore;
|
||||
import com.google.bitcoin.store.UnreadableWalletException;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
@ -34,7 +35,6 @@ import java.awt.datatransfer.StringSelection;
|
|||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
|
@ -125,7 +125,7 @@ public class ToyWallet {
|
|||
walletFile = new File("toy.wallet");
|
||||
try {
|
||||
wallet = Wallet.loadFromFile(walletFile);
|
||||
} catch (IOException e) {
|
||||
} catch (UnreadableWalletException e) {
|
||||
wallet = new Wallet(params);
|
||||
|
||||
// Allow user to specify the first key on the command line as:
|
||||
|
|
Loading…
Add table
Reference in a new issue