/**
   * 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.
   *
   * <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.
   *
   * @throws UnreadableWalletException thrown in various error conditions (see description).
   */
  public Wallet readWallet(
      NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto)
      throws UnreadableWalletException {
    if (walletProto.getVersion() > 1) throw new UnreadableWalletException.FutureVersion();
    if (!walletProto.getNetworkIdentifier().equals(params.getId()))
      throw new UnreadableWalletException.WrongNetwork();

    int sigsRequiredToSpend = walletProto.getSigsRequiredToSpend();

    // Read the scrypt parameters that specify how encryption and decryption is performed.
    KeyChainGroup chain;
    if (walletProto.hasEncryptionParameters()) {
      Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
      final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters);
      chain =
          KeyChainGroup.fromProtobufEncrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter);
    } else {
      chain =
          KeyChainGroup.fromProtobufUnencrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend);
    }
    Wallet wallet = factory.create(params, chain);

    List<Script> scripts = Lists.newArrayList();
    for (Protos.Script protoScript : walletProto.getWatchedScriptList()) {
      try {
        Script script =
            new Script(
                protoScript.getProgram().toByteArray(), protoScript.getCreationTimestamp() / 1000);
        scripts.add(script);
      } catch (ScriptException e) {
        throw new UnreadableWalletException("Unparseable script in wallet");
      }
    }

    wallet.addWatchedScripts(scripts);

    if (walletProto.hasDescription()) {
      wallet.setDescription(walletProto.getDescription());
    }

    // Read all transactions and insert into the txMap.
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      readTransaction(txProto, wallet.getParams());
    }

    // Update transaction outputs to point to inputs that spend them
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      WalletTransaction wtx = connectTransactionOutputs(txProto);
      wallet.addWalletTransaction(wtx);
    }

    // Update the lastBlockSeenHash.
    if (!walletProto.hasLastSeenBlockHash()) {
      wallet.setLastBlockSeenHash(null);
    } else {
      wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash()));
    }
    if (!walletProto.hasLastSeenBlockHeight()) {
      wallet.setLastBlockSeenHeight(-1);
    } else {
      wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
    }
    // Will default to zero if not present.
    wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs());

    if (walletProto.hasKeyRotationTime()) {
      wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000));
    }

    loadExtensions(wallet, extensions != null ? extensions : new WalletExtension[0], walletProto);

    for (Protos.Tag tag : walletProto.getTagsList()) {
      wallet.setTag(tag.getTag(), tag.getData());
    }

    for (Protos.TransactionSigner signerProto : walletProto.getTransactionSignersList()) {
      try {
        Class signerClass = Class.forName(signerProto.getClassName());
        TransactionSigner signer = (TransactionSigner) signerClass.newInstance();
        signer.deserialize(signerProto.getData().toByteArray());
        wallet.addTransactionSigner(signer);
      } catch (Exception e) {
        throw new UnreadableWalletException(
            "Unable to deserialize TransactionSigner instance: " + signerProto.getClassName(), e);
      }
    }

    if (walletProto.hasVersion()) {
      wallet.setVersion(walletProto.getVersion());
    }

    // Make sure the object can be re-used to read another wallet without corruption.
    txMap.clear();

    return wallet;
  }