/**
   * Add a wallet to the BlockChain. Note that the wallet will be unaffected by any blocks received
   * while it was not part of this BlockChain. This method is useful if the wallet has just been
   * created, and its keys have never been in use, or if the wallet has been loaded along with the
   * BlockChain. Note that adding multiple wallets is not well tested!
   */
  public void addWallet(Wallet wallet) {
    addListener(wallet, Threading.SAME_THREAD);
    int walletHeight = wallet.getLastBlockSeenHeight();
    int chainHeight = getBestChainHeight();
    if (walletHeight != chainHeight) {
      log.warn("Wallet/chain height mismatch: {} vs {}", walletHeight, chainHeight);
      log.warn(
          "Hashes: {} vs {}", wallet.getLastBlockSeenHash(), getChainHead().getHeader().getHash());

      // This special case happens when the VM crashes because of a transaction received. It causes
      // the updated
      // block store to persist, but not the wallet. In order to fix the issue, we roll back the
      // block store to
      // the wallet height to make it look like as if the block has never been received.
      if (walletHeight < chainHeight && walletHeight > 0) {
        try {
          rollbackBlockStore(walletHeight);
          log.info("Rolled back block store to height {}.", walletHeight);
        } catch (BlockStoreException x) {
          log.warn(
              "Rollback of block store failed, continuing with mismatched heights. This can happen due to a replay.");
        }
      }
    }
  }
  /**
   * Converts the given wallet to the object representation of the protocol buffers. This can be
   * modified, or additional data fields set, before serialization takes place.
   */
  public Protos.Wallet walletToProto(Wallet wallet) {
    Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
    walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId());
    if (wallet.getDescription() != null) {
      walletBuilder.setDescription(wallet.getDescription());
    }

    for (WalletTransaction wtx : wallet.getWalletTransactions()) {
      Protos.Transaction txProto = makeTxProto(wtx);
      walletBuilder.addTransaction(txProto);
    }

    walletBuilder.addAllKey(wallet.serializeKeychainToProtobuf());

    for (Script script : wallet.getWatchedScripts()) {
      Protos.Script protoScript =
          Protos.Script.newBuilder()
              .setProgram(ByteString.copyFrom(script.getProgram()))
              .setCreationTimestamp(script.getCreationTimeSeconds() * 1000)
              .build();

      walletBuilder.addWatchedScript(protoScript);
    }

    // Populate the lastSeenBlockHash field.
    Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
    if (lastSeenBlockHash != null) {
      walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash));
      walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight());
    }
    if (wallet.getLastBlockSeenTimeSecs() > 0)
      walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs());

    // Populate the scrypt parameters.
    KeyCrypter keyCrypter = wallet.getKeyCrypter();
    if (keyCrypter == null) {
      // The wallet is unencrypted.
      walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED);
    } else {
      // The wallet is encrypted.
      walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType());
      if (keyCrypter instanceof KeyCrypterScrypt) {
        KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter;
        walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters());
      } else {
        // Some other form of encryption has been specified that we do not know how to persist.
        throw new RuntimeException(
            "The wallet has encryption of type '"
                + keyCrypter.getUnderstoodEncryptionType()
                + "' but this WalletProtobufSerializer does not know how to persist this.");
      }
    }

    if (wallet.getKeyRotationTime() != null) {
      long timeSecs = wallet.getKeyRotationTime().getTime() / 1000;
      walletBuilder.setKeyRotationTime(timeSecs);
    }

    populateExtensions(wallet, walletBuilder);

    for (Map.Entry<String, ByteString> entry : wallet.getTags().entrySet()) {
      Protos.Tag.Builder tag =
          Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue());
      walletBuilder.addTags(tag);
    }

    for (TransactionSigner signer : wallet.getTransactionSigners()) {
      // do not serialize LocalTransactionSigner as it's being added implicitly
      if (signer instanceof LocalTransactionSigner) continue;
      Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder();
      protoSigner.setClassName(signer.getClass().getName());
      protoSigner.setData(ByteString.copyFrom(signer.serialize()));
      walletBuilder.addTransactionSigners(protoSigner);
    }

    walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend());

    // Populate the wallet version.
    walletBuilder.setVersion(wallet.getVersion());

    return walletBuilder.build();
  }