diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index a70264148..72e9493c7 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -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"); } diff --git a/core/src/main/java/com/google/bitcoin/store/UnreadableWalletException.java b/core/src/main/java/com/google/bitcoin/store/UnreadableWalletException.java new file mode 100644 index 000000000..43cba410a --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/store/UnreadableWalletException.java @@ -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); + } +} diff --git a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java index f62cf2117..e9df066c1 100644 --- a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java +++ b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java @@ -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 + *
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.
+ * + *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.
+ * + * @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 + *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.
* - * @throws IOException if there is a problem reading the stream. - * @throws IllegalArgumentException if the wallet is corrupt. + *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.
+ * + * @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