private void loadExtensions(
     Wallet wallet, WalletExtension[] extensionsList, Protos.Wallet walletProto)
     throws UnreadableWalletException {
   final Map<String, WalletExtension> extensions = new HashMap<String, WalletExtension>();
   for (WalletExtension e : extensionsList) extensions.put(e.getWalletExtensionID(), e);
   // The Wallet object, if subclassed, might have added some extensions to itself already. In that
   // case, don't
   // expect them to be passed in, just fetch them here and don't re-add.
   extensions.putAll(wallet.getExtensions());
   for (Protos.Extension extProto : walletProto.getExtensionList()) {
     String id = extProto.getId();
     WalletExtension extension = extensions.get(id);
     if (extension == null) {
       if (extProto.getMandatory()) {
         if (requireMandatoryExtensions)
           throw new UnreadableWalletException("Unknown mandatory extension in wallet: " + id);
         else log.error("Unknown extension in wallet {}, ignoring", id);
       }
     } else {
       log.info("Loading wallet extension {}", id);
       try {
         extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray());
         wallet.addOrGetExistingExtension(extension);
       } catch (Exception e) {
         if (extProto.getMandatory() && requireMandatoryExtensions)
           throw new UnreadableWalletException(
               "Could not parse mandatory extension in wallet: " + id);
         else log.error("Error whilst reading extension {}, ignoring", id, e);
       }
     }
   }
 }
  private void readTransaction(Protos.Transaction txProto, NetworkParameters params)
      throws UnreadableWalletException {
    Transaction tx = new Transaction(params);
    if (txProto.hasUpdatedAt()) {
      tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
    }

    for (Protos.TransactionOutput outputProto : txProto.getTransactionOutputList()) {
      Coin value = Coin.valueOf(outputProto.getValue());
      byte[] scriptBytes = outputProto.getScriptBytes().toByteArray();
      TransactionOutput output = new TransactionOutput(params, tx, value, scriptBytes);
      tx.addOutput(output);
    }

    for (Protos.TransactionInput inputProto : txProto.getTransactionInputList()) {
      byte[] scriptBytes = inputProto.getScriptBytes().toByteArray();
      TransactionOutPoint outpoint =
          new TransactionOutPoint(
              params,
              inputProto.getTransactionOutPointIndex() & 0xFFFFFFFFL,
              byteStringToHash(inputProto.getTransactionOutPointHash()));
      Coin value = inputProto.hasValue() ? Coin.valueOf(inputProto.getValue()) : null;
      TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint, value);
      if (inputProto.hasSequence()) {
        input.setSequenceNumber(inputProto.getSequence());
      }
      tx.addInput(input);
    }

    for (int i = 0; i < txProto.getBlockHashCount(); i++) {
      ByteString blockHash = txProto.getBlockHash(i);
      int relativityOffset = 0;
      if (txProto.getBlockRelativityOffsetsCount() > 0)
        relativityOffset = txProto.getBlockRelativityOffsets(i);
      tx.addBlockAppearance(byteStringToHash(blockHash), relativityOffset);
    }

    if (txProto.hasLockTime()) {
      tx.setLockTime(0xffffffffL & txProto.getLockTime());
    }

    if (txProto.hasPurpose()) {
      switch (txProto.getPurpose()) {
        case UNKNOWN:
          tx.setPurpose(Transaction.Purpose.UNKNOWN);
          break;
        case USER_PAYMENT:
          tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
          break;
        case KEY_ROTATION:
          tx.setPurpose(Transaction.Purpose.KEY_ROTATION);
          break;
        case ASSURANCE_CONTRACT_CLAIM:
          tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM);
          break;
        case ASSURANCE_CONTRACT_PLEDGE:
          tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE);
          break;
        case ASSURANCE_CONTRACT_STUB:
          tx.setPurpose(Transaction.Purpose.ASSURANCE_CONTRACT_STUB);
          break;
        default:
          throw new RuntimeException("New purpose serialization not implemented");
      }
    } else {
      // Old wallet: assume a user payment as that's the only reason a new tx would have been
      // created back then.
      tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
    }

    if (txProto.hasExchangeRate()) {
      Protos.ExchangeRate exchangeRateProto = txProto.getExchangeRate();
      tx.setExchangeRate(
          new ExchangeRate(
              Coin.valueOf(exchangeRateProto.getCoinValue()),
              Fiat.valueOf(
                  exchangeRateProto.getFiatCurrencyCode(), exchangeRateProto.getFiatValue())));
    }

    if (txProto.hasMemo()) tx.setMemo(txProto.getMemo());

    // Peercoin: Include time
    tx.setTime(txProto.getTime());

    // Transaction should now be complete.
    Sha256Hash protoHash = 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);
  }