ContractService.java

  1. /*
  2.  * @copyright defined in LICENSE.txt
  3.  */

  4. package ship.build.web.service;

  5. import static java.util.Arrays.stream;
  6. import static java.util.UUID.randomUUID;
  7. import static ship.util.Messages.bind;

  8. import hera.api.AccountOperation;
  9. import hera.api.AergoApi;
  10. import hera.api.ContractOperation;
  11. import hera.api.encode.Base58;
  12. import hera.api.encode.Base58WithCheckSum;
  13. import hera.api.model.Account;
  14. import hera.api.model.AccountAddress;
  15. import hera.api.model.AccountState;
  16. import hera.api.model.Authentication;
  17. import hera.api.model.ContractAddress;
  18. import hera.api.model.ContractDefinition;
  19. import hera.api.model.ContractFunction;
  20. import hera.api.model.ContractInterface;
  21. import hera.api.model.ContractInvocation;
  22. import hera.api.model.ContractResult;
  23. import hera.api.model.ContractTxHash;
  24. import hera.api.model.ContractTxReceipt;
  25. import hera.api.model.Fee;
  26. import hera.exception.RpcConnectionException;
  27. import hera.exception.RpcException;
  28. import hera.util.Pair;
  29. import java.io.ByteArrayInputStream;
  30. import java.io.IOException;
  31. import java.util.ArrayList;
  32. import java.util.HashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. import javax.inject.Named;
  36. import lombok.Getter;
  37. import lombok.NoArgsConstructor;
  38. import lombok.NonNull;
  39. import lombok.RequiredArgsConstructor;
  40. import lombok.Setter;
  41. import org.springframework.beans.factory.annotation.Value;
  42. import ship.build.web.exception.AergoNodeException;
  43. import ship.build.web.exception.ResourceNotFoundException;
  44. import ship.build.web.model.BuildDetails;
  45. import ship.build.web.model.DeploymentResult;
  46. import ship.build.web.model.ExecutionResult;
  47. import ship.build.web.model.QueryResult;
  48. import ship.test.LuaBinary;
  49. import ship.test.LuaCompiler;
  50. import ship.util.AergoPool;
  51. import ship.util.ResourcePool;

  52. @NoArgsConstructor
  53. @Named
  54. public class ContractService extends AbstractService {

  55.   protected static final String NL_0 = ContractService.class.getName() + ".0";

  56.   @Getter
  57.   @Setter
  58.   @Value("${project.endpoint}")
  59.   protected String endpoint;

  60.   protected String password = randomUUID().toString();

  61.   protected Account account;


  62.   protected Fee fee = new Fee(1000, 1000);

  63.   protected final LuaCompiler luaCompiler = new LuaCompiler();

  64.   protected final List<DeploymentResult> deployHistory = new ArrayList<>();

  65.   protected final Map<String, DeploymentResult> encodedContractTxHash2contractAddresses =
  66.       new HashMap<>();

  67.   @Setter
  68.   protected ResourcePool<AergoApi> aergoPool;

  69.   @RequiredArgsConstructor
  70.   class SimpleBase58 implements Base58 {

  71.     @NonNull
  72.     protected final String encoded;

  73.     @Override
  74.     public String getEncodedValue() {
  75.       return encoded;
  76.     }
  77.   }

  78.   protected synchronized void ensureAccount() {
  79.     if (null != account) {
  80.       return;
  81.     }

  82.     if (null == aergoPool) {
  83.       aergoPool = new AergoPool(endpoint);
  84.     }
  85.     final AergoApi aergoApi = aergoPool.borrowResource();
  86.     try {
  87.       final AccountOperation accountOperation = aergoApi.getAccountOperation();
  88.       logger.trace("Password: {}", password);
  89.       account = accountOperation.create(password);
  90.       final AccountAddress accountAddress = account.getAddress();
  91.       final Authentication authentication = new Authentication(accountAddress, password);
  92.       accountOperation.unlock(authentication);
  93.       logger.debug("{} unlocked", authentication);
  94.     } catch (final RpcConnectionException ex) {
  95.       throw new AergoNodeException(
  96.           "Fail to connect aergo[" + endpoint + "]. Check your aergo node.", ex);
  97.     } catch (final RpcException ex) {
  98.       throw new AergoNodeException("Fail to deploy contract", ex);
  99.     } finally {
  100.       aergoPool.returnResource(aergoApi);
  101.     }

  102.   }

  103.   /**
  104.    * Deploy {@code buildDetails}'s result.
  105.    *
  106.    * @param buildDetails build result
  107.    *
  108.    * @return deployment result
  109.    *
  110.    * @throws Exception Fail to deploy
  111.    */
  112.   public DeploymentResult deploy(final BuildDetails buildDetails) throws Exception {
  113.     ensureAccount();

  114.     final AergoApi aergoApi = aergoPool.borrowResource();
  115.     try {
  116.       final byte[] buildResult = buildDetails.getResult().getBytes();
  117.       final LuaBinary luaBinary = luaCompiler.compile(() -> new ByteArrayInputStream(buildResult));
  118.       logger.trace("Successful to compile:\n{}", luaBinary.getPayload());
  119.       final AccountOperation accountOperation = aergoApi.getAccountOperation();
  120.       final AccountState syncedAccount = accountOperation.getState(account);
  121.       final ContractOperation contractOperation = aergoApi.getContractOperation();
  122.       final Base58WithCheckSum encodedPayload = () -> luaBinary.getPayload().getEncodedValue();

  123.       account.setNonce(syncedAccount.getNonce() + 1);
  124.       final ContractDefinition contractDefinition = ContractDefinition.of(encodedPayload);
  125.       final ContractTxHash contractTransactionHash =
  126.           contractOperation.deploy(account, contractDefinition, fee);
  127.       logger.debug("Contract transaction hash: {}", contractTransactionHash);
  128.       final String encodedContractTxHash = contractTransactionHash.toString();
  129.       final DeploymentResult deploymentResult = new DeploymentResult();
  130.       deploymentResult.setBuildUuid(buildDetails.getUuid());
  131.       deploymentResult.setEncodedContractTransactionHash(encodedContractTxHash);
  132.       encodedContractTxHash2contractAddresses.put(encodedContractTxHash, deploymentResult);
  133.       deployHistory.add(deploymentResult);
  134.       return deploymentResult;
  135.     } catch (final RpcConnectionException ex) {
  136.       throw new AergoNodeException(
  137.           "Fail to connect aergo[" + endpoint + "]. Check your aergo node.", ex);
  138.     } catch (final RpcException ex) {
  139.       throw new AergoNodeException("Fail to deploy contract", ex);
  140.     } finally {
  141.       aergoPool.returnResource(aergoApi);
  142.     }
  143.   }

  144.   protected Pair<ContractTxHash, ContractFunction> find(
  145.       final String encodedContractTxHash, final String functionName) {
  146.     logger.trace("Encoded tx hash: {}", encodedContractTxHash);
  147.     final Base58 encoded = new SimpleBase58(encodedContractTxHash);
  148.     final ContractTxHash contractTxHash = new ContractTxHash(encoded);

  149.     final DeploymentResult deploymentResult =
  150.         encodedContractTxHash2contractAddresses.get(encodedContractTxHash);
  151.     final ContractInterface contractInterface = deploymentResult.getContractInterface();
  152.     final ContractFunction contractFunction = contractInterface.findFunction(functionName)
  153.         .orElseThrow(() -> new ResourceNotFoundException("No " + functionName + " function."));
  154.     return new Pair<>(contractTxHash, contractFunction);
  155.   }

  156.   /**
  157.    * Execute smart contract.
  158.    *
  159.    * @param encodedContractTxHash contract transaction hash
  160.    * @param functionName          function's name to execute
  161.    * @param args                  function's arguments to execute
  162.    *
  163.    * @return execution result
  164.    */
  165.   public ExecutionResult tryExecute(final String encodedContractTxHash, final String functionName,
  166.       final String... args) {
  167.     logger.trace("Encoded tx hash: {}", encodedContractTxHash);
  168.     final Pair<ContractTxHash, ContractFunction> pair = find(encodedContractTxHash, functionName);
  169.     return execute(pair.v1, pair.v2, args);
  170.   }

  171.   protected ExecutionResult execute(
  172.       final ContractTxHash contractTxHash,
  173.       final ContractFunction contractFunction,
  174.       final String... args) {
  175.     ensureAccount();
  176.     final AergoApi aergoApi = aergoPool.borrowResource();
  177.     try {
  178.       final AccountOperation accountOperation = aergoApi.getAccountOperation();
  179.       final ContractOperation contractOperation = aergoApi.getContractOperation();
  180.       final ContractTxReceipt contractTxReceipt =
  181.           contractOperation.getReceipt(contractTxHash);
  182.       logger.debug("Receipt: {}", contractTxReceipt);
  183.       final AccountState syncedAccount = accountOperation.getState(account);
  184.       final ContractAddress contractAddress = contractTxReceipt.getContractAddress();

  185.       logger.trace("Executing...");
  186.       final ContractInvocation contractCall =
  187.           new ContractInvocation(contractAddress, contractFunction, stream(args).toArray());
  188.       account.setNonce(syncedAccount.getNonce() + 1);
  189.       final ContractTxHash executionContractHash = contractOperation.execute(
  190.           account,
  191.           contractCall,
  192.           fee
  193.       );

  194.       final ExecutionResult executionResult = new ExecutionResult();
  195.       executionResult.setContractTransactionHash(executionContractHash.toString());
  196.       return executionResult;
  197.     } finally {
  198.       aergoPool.returnResource(aergoApi);
  199.     }
  200.   }

  201.   /**
  202.    * Query smart contract.
  203.    *
  204.    * @param encodedContractTxHash contract transaction hash
  205.    * @param functionName          function's name to execute
  206.    * @param args                  function's arguments to execute
  207.    *
  208.    * @return query result
  209.    */
  210.   public QueryResult tryQuery(final String encodedContractTxHash, final String functionName,
  211.       final String... args) {
  212.     logger.trace("Encoded tx hash: {}", encodedContractTxHash);
  213.     final Pair<ContractTxHash, ContractFunction> pair = find(encodedContractTxHash, functionName);
  214.     return query(pair.v1, pair.v2, args);
  215.   }

  216.   protected QueryResult query(
  217.       final ContractTxHash contractTxHash,
  218.       final ContractFunction contractFunction,
  219.       final String... args) {
  220.     ensureAccount();
  221.     final AergoApi aergoApi = aergoPool.borrowResource();
  222.     try {
  223.       final ContractOperation contractOperation = aergoApi.getContractOperation();
  224.       final ContractTxReceipt contractTxReceipt =
  225.           contractOperation.getReceipt(contractTxHash);
  226.       logger.debug("Receipt: {}", contractTxReceipt);
  227.       final ContractAddress contractAddress = contractTxReceipt.getContractAddress();

  228.       final ContractInvocation contractCall =
  229.           new ContractInvocation(contractAddress, contractFunction, stream(args).toArray());
  230.       logger.trace("Querying...");
  231.       final ContractResult contractResult = contractOperation.query(contractCall);
  232.       final String resultString = new String(contractResult.getResultInRawBytes().getValue());
  233.       return new QueryResult(resultString);
  234.     } finally {
  235.       aergoPool.returnResource(aergoApi);
  236.     }
  237.   }

  238.   /**
  239.    * Get latest contract.
  240.    *
  241.    * @return latest deployed contract
  242.    */
  243.   public DeploymentResult getLatestContractInformation() {
  244.     if (deployHistory.isEmpty()) {
  245.       throw new ResourceNotFoundException("No deployment!! Deploy your contract first.");
  246.     }
  247.     final DeploymentResult latest = deployHistory.get(deployHistory.size() - 1);
  248.     logger.debug("Latest deployment: {}", latest);
  249.     if (null == latest.getContractInterface()) {
  250.       final String encodedContractTxHash = latest.getEncodedContractTransactionHash();
  251.       logger.trace("Encoded tx hash: {}", encodedContractTxHash);
  252.       final Base58 base58 = new SimpleBase58(encodedContractTxHash);
  253.       final ContractTxHash contractTxHash = new ContractTxHash(base58);
  254.       final ContractInterface contractInterface = getInterface(contractTxHash);
  255.       latest.setContractInterface(contractInterface);
  256.     }
  257.     return latest;
  258.   }

  259.   /**
  260.    * Get application blockchain interface for {@code encodedContractTransactionHash}
  261.    * from {@code endpoint}.
  262.    *
  263.    * @param contractTxHash contract's transaction hash
  264.    *
  265.    * @return abi set
  266.    */
  267.   public ContractInterface getInterface(final ContractTxHash contractTxHash) {
  268.     final AergoApi aergoApi = aergoPool.borrowResource();
  269.     try {
  270.       final ContractOperation contractOperation = aergoApi.getContractOperation();
  271.       ContractTxReceipt receipt = contractOperation.getReceipt(contractTxHash);
  272.       final ContractAddress address = receipt.getContractAddress();
  273.       final ContractInterface contractInterface = contractOperation.getContractInterface(address);
  274.       if (null == contractInterface) {
  275.         throw new ResourceNotFoundException(bind(NL_0, contractTxHash));
  276.       }
  277.       return contractInterface;
  278.     } finally {
  279.       aergoPool.returnResource(aergoApi);
  280.     }
  281.   }
  282. }