private WalletTransaction connectTransactionOutputs(
      org.bitcoinj.wallet.Protos.Transaction txProto) throws UnreadableWalletException {
    Transaction tx = txMap.get(txProto.getHash());
    final WalletTransaction.Pool pool;
    switch (txProto.getPool()) {
      case DEAD:
        pool = WalletTransaction.Pool.DEAD;
        break;
      case PENDING:
        pool = WalletTransaction.Pool.PENDING;
        break;
      case SPENT:
        pool = WalletTransaction.Pool.SPENT;
        break;
      case UNSPENT:
        pool = WalletTransaction.Pool.UNSPENT;
        break;
        // Upgrade old wallets: inactive pool has been merged with the pending pool.
        // Remove this some time after 0.9 is old and everyone has upgraded.
        // There should not be any spent outputs in this tx as old wallets would not allow them to
        // be spent
        // in this state.
      case INACTIVE:
      case PENDING_INACTIVE:
        pool = WalletTransaction.Pool.PENDING;
        break;
      default:
        throw new UnreadableWalletException("Unknown transaction pool: " + txProto.getPool());
    }
    for (int i = 0; i < tx.getOutputs().size(); i++) {
      TransactionOutput output = tx.getOutputs().get(i);
      final Protos.TransactionOutput transactionOutput = txProto.getTransactionOutput(i);
      if (transactionOutput.hasSpentByTransactionHash()) {
        final ByteString spentByTransactionHash = transactionOutput.getSpentByTransactionHash();
        Transaction spendingTx = txMap.get(spentByTransactionHash);
        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);
      }
    }

    if (txProto.hasConfidence()) {
      Protos.TransactionConfidence confidenceProto = txProto.getConfidence();
      TransactionConfidence confidence = tx.getConfidence();
      readConfidence(tx, confidenceProto, confidence);
    }

    return new WalletTransaction(pool, tx);
  }
  private void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
    Transaction tx = new Transaction(params);
    if (txProto.hasUpdatedAt()) {
      tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
    }

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

    for (Protos.TransactionInput transactionInput : txProto.getTransactionInputList()) {
      byte[] scriptBytes = transactionInput.getScriptBytes().toByteArray();
      TransactionOutPoint outpoint =
          new TransactionOutPoint(
              params,
              transactionInput.getTransactionOutPointIndex(),
              byteStringToHash(transactionInput.getTransactionOutPointHash()));
      TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint);
      if (transactionInput.hasSequence()) {
        input.setSequenceNumber(transactionInput.getSequence());
      }
      tx.addInput(input);
    }

    for (ByteString blockHash : txProto.getBlockHashList()) {
      tx.addBlockAppearance(byteStringToHash(blockHash));
    }

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

    // 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()));
    txMap.put(txProto.getHash(), tx);
  }
  private WalletTransaction connectTransactionOutputs(
      org.bitcoinj.wallet.Protos.Transaction txProto) {
    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) {
      // Upgrade old wallets: inactive pool has been merged with the pending pool.
      // Remove this some time after 0.9 is old and everyone has upgraded.
      // There should not be any spent outputs in this tx as old wallets would not allow them to be
      // spent
      // in this state.
      pool = WalletTransaction.Pool.PENDING;
    }
    for (int i = 0; i < tx.getOutputs().size(); i++) {
      TransactionOutput output = tx.getOutputs().get(i);
      final Protos.TransactionOutput transactionOutput = txProto.getTransactionOutput(i);
      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",
                  tx.getHashAsString(), byteStringToHash(spentByTransactionHash)));
        final int spendingIndex = transactionOutput.getSpentByTransactionIndex();
        TransactionInput input = checkNotNull(spendingTx.getInput(spendingIndex));
        input.connect(output);
      }
    }

    if (txProto.hasConfidence()) {
      Protos.TransactionConfidence confidenceProto = txProto.getConfidence();
      TransactionConfidence confidence = tx.getConfidence();
      readConfidence(tx, confidenceProto, confidence);
    }

    return new WalletTransaction(pool, tx);
  }
  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());

    // 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);
  }
  private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
    Transaction tx = wtx.getTransaction();
    Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();

    txBuilder
        .setPool(getProtoPool(wtx))
        .setHash(hashToByteString(tx.getHash()))
        .setVersion((int) tx.getVersion());

    if (tx.getUpdateTime() != null) {
      txBuilder.setUpdatedAt(tx.getUpdateTime().getTime());
    }

    if (tx.getLockTime() > 0) {
      txBuilder.setLockTime((int) tx.getLockTime());
    }

    // Handle inputs.
    for (TransactionInput input : tx.getInputs()) {
      Protos.TransactionInput.Builder inputBuilder =
          Protos.TransactionInput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(input.getScriptBytes()))
              .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash()))
              .setTransactionOutPointIndex((int) input.getOutpoint().getIndex());
      if (input.hasSequence()) inputBuilder.setSequence((int) input.getSequenceNumber());
      if (input.getValue() != null) inputBuilder.setValue(input.getValue().value);
      txBuilder.addTransactionInput(inputBuilder);
    }

    // Handle outputs.
    for (TransactionOutput output : tx.getOutputs()) {
      Protos.TransactionOutput.Builder outputBuilder =
          Protos.TransactionOutput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
              .setValue(output.getValue().value);
      final TransactionInput spentBy = output.getSpentBy();
      if (spentBy != null) {
        Sha256Hash spendingHash = spentBy.getParentTransaction().getHash();
        int spentByTransactionIndex = spentBy.getParentTransaction().getInputs().indexOf(spentBy);
        outputBuilder
            .setSpentByTransactionHash(hashToByteString(spendingHash))
            .setSpentByTransactionIndex(spentByTransactionIndex);
      }
      txBuilder.addTransactionOutput(outputBuilder);
    }

    // Handle which blocks tx was seen in.
    final Map<Sha256Hash, Integer> appearsInHashes = tx.getAppearsInHashes();
    if (appearsInHashes != null) {
      for (Map.Entry<Sha256Hash, Integer> entry : appearsInHashes.entrySet()) {
        txBuilder.addBlockHash(hashToByteString(entry.getKey()));
        txBuilder.addBlockRelativityOffsets(entry.getValue());
      }
    }

    if (tx.hasConfidence()) {
      TransactionConfidence confidence = tx.getConfidence();
      Protos.TransactionConfidence.Builder confidenceBuilder =
          Protos.TransactionConfidence.newBuilder();
      writeConfidence(txBuilder, confidence, confidenceBuilder);
    }

    Protos.Transaction.Purpose purpose;
    switch (tx.getPurpose()) {
      case UNKNOWN:
        purpose = Protos.Transaction.Purpose.UNKNOWN;
        break;
      case USER_PAYMENT:
        purpose = Protos.Transaction.Purpose.USER_PAYMENT;
        break;
      case KEY_ROTATION:
        purpose = Protos.Transaction.Purpose.KEY_ROTATION;
        break;
      case ASSURANCE_CONTRACT_CLAIM:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM;
        break;
      case ASSURANCE_CONTRACT_PLEDGE:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE;
        break;
      case ASSURANCE_CONTRACT_STUB:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_STUB;
        break;
      default:
        throw new RuntimeException("New tx purpose serialization not implemented.");
    }
    txBuilder.setPurpose(purpose);

    ExchangeRate exchangeRate = tx.getExchangeRate();
    if (exchangeRate != null) {
      Protos.ExchangeRate.Builder exchangeRateBuilder =
          Protos.ExchangeRate.newBuilder()
              .setCoinValue(exchangeRate.coin.value)
              .setFiatValue(exchangeRate.fiat.value)
              .setFiatCurrencyCode(exchangeRate.fiat.currencyCode);
      txBuilder.setExchangeRate(exchangeRateBuilder);
    }

    if (tx.getMemo() != null) txBuilder.setMemo(tx.getMemo());

    return txBuilder.build();
  }
  private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
    Transaction tx = wtx.getTransaction();
    Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();

    txBuilder
        .setPool(Protos.Transaction.Pool.valueOf(wtx.getPool().getValue()))
        .setHash(hashToByteString(tx.getHash()))
        .setVersion((int) tx.getVersion());

    if (tx.getUpdateTime() != null) {
      txBuilder.setUpdatedAt(tx.getUpdateTime().getTime());
    }

    if (tx.getLockTime() > 0) {
      txBuilder.setLockTime((int) tx.getLockTime());
    }

    // Handle inputs.
    for (TransactionInput input : tx.getInputs()) {
      Protos.TransactionInput.Builder inputBuilder =
          Protos.TransactionInput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(input.getScriptBytes()))
              .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash()))
              .setTransactionOutPointIndex((int) input.getOutpoint().getIndex());
      if (input.hasSequence()) {
        inputBuilder.setSequence((int) input.getSequenceNumber());
      }
      txBuilder.addTransactionInput(inputBuilder);
    }

    // Handle outputs.
    for (TransactionOutput output : tx.getOutputs()) {
      Protos.TransactionOutput.Builder outputBuilder =
          Protos.TransactionOutput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
              .setValue(output.getValue().longValue());
      final TransactionInput spentBy = output.getSpentBy();
      if (spentBy != null) {
        Sha256Hash spendingHash = spentBy.getParentTransaction().getHash();
        int spentByTransactionIndex = spentBy.getParentTransaction().getInputs().indexOf(spentBy);
        outputBuilder
            .setSpentByTransactionHash(hashToByteString(spendingHash))
            .setSpentByTransactionIndex(spentByTransactionIndex);
      }
      txBuilder.addTransactionOutput(outputBuilder);
    }

    // Handle which blocks tx was seen in.
    if (tx.getAppearsInHashes() != null) {
      for (Sha256Hash hash : tx.getAppearsInHashes()) {
        txBuilder.addBlockHash(hashToByteString(hash));
      }
    }

    if (tx.hasConfidence()) {
      TransactionConfidence confidence = tx.getConfidence();
      Protos.TransactionConfidence.Builder confidenceBuilder =
          Protos.TransactionConfidence.newBuilder();
      writeConfidence(txBuilder, confidence, confidenceBuilder);
    }

    return txBuilder.build();
  }