KeyStoreBaseTemplate.java

/*
 * @copyright defined in LICENSE.txt
 */

package hera.client.internal;

import static hera.util.TransportUtils.sha256AndEncodeHexa;
import static org.slf4j.LoggerFactory.getLogger;
import static types.AergoRPCServiceGrpc.newFutureStub;

import hera.ContextProvider;
import hera.ContextProviderInjectable;
import hera.annotation.ApiAudience;
import hera.annotation.ApiStability;
import hera.api.function.Function0;
import hera.api.function.Function1;
import hera.api.function.Function3;
import hera.api.model.AccountAddress;
import hera.api.model.Authentication;
import hera.api.model.EncryptedPrivateKey;
import hera.api.model.RawTransaction;
import hera.api.model.Transaction;
import hera.client.ChannelInjectable;
import hera.transport.AccountAddressConverterFactory;
import hera.transport.AuthenticationConverterFactory;
import hera.transport.EncryptedPrivateKeyConverterFactory;
import hera.transport.ModelConverter;
import hera.transport.TransactionConverterFactory;
import io.grpc.ManagedChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import lombok.Getter;
import org.slf4j.Logger;
import types.AccountOuterClass;
import types.AergoRPCServiceGrpc.AergoRPCServiceFutureStub;
import types.Blockchain;
import types.Rpc;

@ApiAudience.Private
@ApiStability.Unstable
public class KeyStoreBaseTemplate implements ChannelInjectable, ContextProviderInjectable {

  protected final transient Logger logger = getLogger(getClass());

  protected final ModelConverter<EncryptedPrivateKey, Rpc.SingleBytes> encryptedPkConverter =
      new EncryptedPrivateKeyConverterFactory().create();

  protected final ModelConverter<AccountAddress,
      com.google.protobuf.ByteString> accountAddressConverter =
          new AccountAddressConverterFactory().create();

  protected final ModelConverter<Authentication, Rpc.Personal> authenticationConverter =
      new AuthenticationConverterFactory().create();

  protected final ModelConverter<Transaction, Blockchain.Tx> transactionConverter =
      new TransactionConverterFactory().create();

  @Getter
  protected AergoRPCServiceFutureStub aergoService;

  protected ContextProvider contextProvider;

  @Override
  public void setChannel(final ManagedChannel channel) {
    this.aergoService = newFutureStub(channel);
  }

  @Override
  public void setContextProvider(final ContextProvider contextProvider) {
    this.contextProvider = contextProvider;
  }

  @Getter
  private final Function0<
      Future<List<AccountAddress>>> listFunction = new Function0<Future<List<AccountAddress>>>() {

        @Override
        public Future<List<AccountAddress>> apply() {
          logger.debug("List keystore addresses");

          final Rpc.Empty empty = Rpc.Empty.newBuilder().build();
          logger.trace("AergoService getAccounts arg: {}", empty);

          final Future<AccountOuterClass.AccountList> rawFuture = aergoService.getAccounts(empty);
          final Future<List<AccountAddress>> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<AccountOuterClass.AccountList, List<AccountAddress>>() {

                @Override
                public List<AccountAddress> apply(
                    final AccountOuterClass.AccountList rpcAccountList) {
                  final List<AccountAddress> domainAccountList = new ArrayList<>();
                  for (final AccountOuterClass.Account rpcAccount : rpcAccountList
                      .getAccountsList()) {
                    final AccountAddress domainAccount =
                        accountAddressConverter.convertToDomainModel(rpcAccount.getAddress());
                    domainAccountList.add(domainAccount);
                  }
                  return domainAccountList;
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function1<String,
      Future<AccountAddress>> createFunction = new Function1<String, Future<AccountAddress>>() {

        @Override
        public Future<AccountAddress> apply(final String password) {
          if (logger.isDebugEnabled()) {
            logger.debug("Create an account to server keystore with password: {}",
                sha256AndEncodeHexa(password));
          }

          final Rpc.Personal rpcPassword =
              Rpc.Personal.newBuilder().setPassphrase(password).build();
          if (logger.isTraceEnabled()) {
            logger.trace("AergoService createAccount arg: {}",
                sha256AndEncodeHexa(rpcPassword.getPassphrase()));
          }

          final Future<AccountOuterClass.Account> rawFuture =
              aergoService.createAccount(rpcPassword);
          final Future<AccountAddress> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<AccountOuterClass.Account, AccountAddress>() {

                @Override
                public AccountAddress apply(final AccountOuterClass.Account rpcAccount) {
                  return accountAddressConverter.convertToDomainModel(rpcAccount.getAddress());
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function1<Authentication,
      Future<Boolean>> unlockFunction = new Function1<Authentication, Future<Boolean>>() {

        @Override
        public Future<Boolean> apply(final Authentication authentication) {
          logger.debug("Unlock an account in server keystore with authentication: {}",
              authentication);

          final Rpc.Personal rpcAuthentication =
              authenticationConverter.convertToRpcModel(authentication);
          if (logger.isTraceEnabled()) {
            logger.trace("AergoService unlockAccount arg: {}, {}",
                rpcAuthentication.getAccount(),
                sha256AndEncodeHexa(rpcAuthentication.getPassphrase()));
          }

          final Future<AccountOuterClass.Account> rawFuture =
              aergoService.unlockAccount(rpcAuthentication);
          final Future<Boolean> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<AccountOuterClass.Account, Boolean>() {

                @Override
                public Boolean apply(final AccountOuterClass.Account rpcAccount) {
                  return null != rpcAccount.getAddress();
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function1<Authentication, Future<Boolean>> lockFunction =
      new Function1<Authentication, Future<Boolean>>() {

        @Override
        public Future<Boolean> apply(final Authentication authentication) {
          logger.debug("Lock an account in server keystore with authentication: {}",
              authentication);

          final Rpc.Personal rpcAuthentication =
              authenticationConverter.convertToRpcModel(authentication);
          if (logger.isTraceEnabled()) {
            logger.trace("AergoService lockAccount arg: {}",
                sha256AndEncodeHexa(rpcAuthentication.getPassphrase()));
          }

          final Future<AccountOuterClass.Account> rawFuture =
              aergoService.lockAccount(rpcAuthentication);
          final Future<Boolean> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<AccountOuterClass.Account, Boolean>() {

                @Override
                public Boolean apply(final AccountOuterClass.Account rpcAccount) {
                  return null != rpcAccount.getAddress();
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function1<RawTransaction,
      Future<Transaction>> signFunction = new Function1<RawTransaction, Future<Transaction>>() {

        @Override
        public Future<Transaction> apply(final RawTransaction rawTransaction) {
          logger.debug("Sign request with rawTx: {}", rawTransaction);

          final Transaction domainTransaction =
              Transaction.newBuilder().rawTransaction(rawTransaction).build();
          final Blockchain.Tx rpcTx = transactionConverter.convertToRpcModel(domainTransaction);
          logger.trace("AergoService signTX arg: {}", rpcTx);

          final Future<Blockchain.Tx> rawFuture = aergoService.signTX(rpcTx);
          final Future<Transaction> convertedFuture =
              HerajFutures.transform(rawFuture, new Function1<Blockchain.Tx, Transaction>() {
                @Override
                public Transaction apply(final Blockchain.Tx tx) {
                  return transactionConverter.convertToDomainModel(tx);
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function3<EncryptedPrivateKey, String, String,
      Future<AccountAddress>> importKeyFunction = new Function3<EncryptedPrivateKey, String, String,
          Future<AccountAddress>>() {

        @Override
        public Future<AccountAddress> apply(final EncryptedPrivateKey encryptedKey,
            final String oldPassword, final String newPassword) {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Import an account to server keystore with "
                    + "encryptedKey: {}, oldPassword: {}, newPassword: {}",
                encryptedKey, sha256AndEncodeHexa(oldPassword),
                sha256AndEncodeHexa(newPassword));
          }

          final Rpc.ImportFormat rpcImport = Rpc.ImportFormat.newBuilder()
              .setWif(encryptedPkConverter.convertToRpcModel(encryptedKey))
              .setOldpass(oldPassword).setNewpass(newPassword).build();
          if (logger.isTraceEnabled()) {
            logger.trace(
                "AergoService importAccount arg: ImportFormat(wif={}, oldPass={}, newPass={})",
                rpcImport.getWif(), sha256AndEncodeHexa(rpcImport.getOldpass()),
                sha256AndEncodeHexa(rpcImport.getNewpass()));
          }

          final Future<AccountOuterClass.Account> rawFuture =
              aergoService.importAccount(rpcImport);
          final Future<AccountAddress> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<AccountOuterClass.Account, AccountAddress>() {

                @Override
                public AccountAddress apply(final AccountOuterClass.Account rpcAccount) {
                  return accountAddressConverter
                      .convertToDomainModel(rpcAccount.getAddress());
                }
              });
          return convertedFuture;
        }
      };

  @Getter
  private final Function1<Authentication, Future<EncryptedPrivateKey>> exportKeyFunction =
      new Function1<Authentication, Future<EncryptedPrivateKey>>() {

        @Override
        public Future<EncryptedPrivateKey> apply(final Authentication authentication) {
          logger.debug("Export an account from server keystore with authentication: {}",
              authentication);

          final Rpc.Personal rpcAuthentication =
              authenticationConverter.convertToRpcModel(authentication);
          if (logger.isTraceEnabled()) {
            logger.trace("AergoService exportAccount  arg: Personal(account={}, password={})",
                rpcAuthentication.getAccount().getAddress(),
                sha256AndEncodeHexa(rpcAuthentication.getPassphrase()));
          }

          final Future<Rpc.SingleBytes> rawFuture =
              aergoService.exportAccount(rpcAuthentication);
          final Future<EncryptedPrivateKey> convertedFuture = HerajFutures.transform(rawFuture,
              new Function1<Rpc.SingleBytes, EncryptedPrivateKey>() {

                @Override
                public EncryptedPrivateKey apply(final Rpc.SingleBytes rawPk) {
                  return encryptedPkConverter.convertToDomainModel(rawPk);
                }
              });
          return convertedFuture;
        }
      };

}