ContractService.java
- /*
- * @copyright defined in LICENSE.txt
- */
- package ship.build.web.service;
- import static java.util.Arrays.stream;
- import static java.util.UUID.randomUUID;
- import static ship.util.Messages.bind;
- import hera.api.AccountOperation;
- import hera.api.AergoApi;
- import hera.api.ContractOperation;
- import hera.api.encode.Base58;
- import hera.api.encode.Base58WithCheckSum;
- import hera.api.model.Account;
- import hera.api.model.AccountAddress;
- import hera.api.model.AccountState;
- import hera.api.model.Authentication;
- import hera.api.model.ContractAddress;
- import hera.api.model.ContractDefinition;
- import hera.api.model.ContractFunction;
- import hera.api.model.ContractInterface;
- import hera.api.model.ContractInvocation;
- import hera.api.model.ContractResult;
- import hera.api.model.ContractTxHash;
- import hera.api.model.ContractTxReceipt;
- import hera.api.model.Fee;
- import hera.exception.RpcConnectionException;
- import hera.exception.RpcException;
- import hera.util.Pair;
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import javax.inject.Named;
- import lombok.Getter;
- import lombok.NoArgsConstructor;
- import lombok.NonNull;
- import lombok.RequiredArgsConstructor;
- import lombok.Setter;
- import org.springframework.beans.factory.annotation.Value;
- import ship.build.web.exception.AergoNodeException;
- import ship.build.web.exception.ResourceNotFoundException;
- import ship.build.web.model.BuildDetails;
- import ship.build.web.model.DeploymentResult;
- import ship.build.web.model.ExecutionResult;
- import ship.build.web.model.QueryResult;
- import ship.test.LuaBinary;
- import ship.test.LuaCompiler;
- import ship.util.AergoPool;
- import ship.util.ResourcePool;
- @NoArgsConstructor
- @Named
- public class ContractService extends AbstractService {
- protected static final String NL_0 = ContractService.class.getName() + ".0";
- @Getter
- @Setter
- @Value("${project.endpoint}")
- protected String endpoint;
- protected String password = randomUUID().toString();
- protected Account account;
- protected Fee fee = new Fee(1000, 1000);
- protected final LuaCompiler luaCompiler = new LuaCompiler();
- protected final List<DeploymentResult> deployHistory = new ArrayList<>();
- protected final Map<String, DeploymentResult> encodedContractTxHash2contractAddresses =
- new HashMap<>();
- @Setter
- protected ResourcePool<AergoApi> aergoPool;
- @RequiredArgsConstructor
- class SimpleBase58 implements Base58 {
- @NonNull
- protected final String encoded;
- @Override
- public String getEncodedValue() {
- return encoded;
- }
- }
- protected synchronized void ensureAccount() {
- if (null != account) {
- return;
- }
- if (null == aergoPool) {
- aergoPool = new AergoPool(endpoint);
- }
- final AergoApi aergoApi = aergoPool.borrowResource();
- try {
- final AccountOperation accountOperation = aergoApi.getAccountOperation();
- logger.trace("Password: {}", password);
- account = accountOperation.create(password);
- final AccountAddress accountAddress = account.getAddress();
- final Authentication authentication = new Authentication(accountAddress, password);
- accountOperation.unlock(authentication);
- logger.debug("{} unlocked", authentication);
- } catch (final RpcConnectionException ex) {
- throw new AergoNodeException(
- "Fail to connect aergo[" + endpoint + "]. Check your aergo node.", ex);
- } catch (final RpcException ex) {
- throw new AergoNodeException("Fail to deploy contract", ex);
- } finally {
- aergoPool.returnResource(aergoApi);
- }
- }
- /**
- * Deploy {@code buildDetails}'s result.
- *
- * @param buildDetails build result
- *
- * @return deployment result
- *
- * @throws Exception Fail to deploy
- */
- public DeploymentResult deploy(final BuildDetails buildDetails) throws Exception {
- ensureAccount();
- final AergoApi aergoApi = aergoPool.borrowResource();
- try {
- final byte[] buildResult = buildDetails.getResult().getBytes();
- final LuaBinary luaBinary = luaCompiler.compile(() -> new ByteArrayInputStream(buildResult));
- logger.trace("Successful to compile:\n{}", luaBinary.getPayload());
- final AccountOperation accountOperation = aergoApi.getAccountOperation();
- final AccountState syncedAccount = accountOperation.getState(account);
- final ContractOperation contractOperation = aergoApi.getContractOperation();
- final Base58WithCheckSum encodedPayload = () -> luaBinary.getPayload().getEncodedValue();
- account.setNonce(syncedAccount.getNonce() + 1);
- final ContractDefinition contractDefinition = ContractDefinition.of(encodedPayload);
- final ContractTxHash contractTransactionHash =
- contractOperation.deploy(account, contractDefinition, fee);
- logger.debug("Contract transaction hash: {}", contractTransactionHash);
- final String encodedContractTxHash = contractTransactionHash.toString();
- final DeploymentResult deploymentResult = new DeploymentResult();
- deploymentResult.setBuildUuid(buildDetails.getUuid());
- deploymentResult.setEncodedContractTransactionHash(encodedContractTxHash);
- encodedContractTxHash2contractAddresses.put(encodedContractTxHash, deploymentResult);
- deployHistory.add(deploymentResult);
- return deploymentResult;
- } catch (final RpcConnectionException ex) {
- throw new AergoNodeException(
- "Fail to connect aergo[" + endpoint + "]. Check your aergo node.", ex);
- } catch (final RpcException ex) {
- throw new AergoNodeException("Fail to deploy contract", ex);
- } finally {
- aergoPool.returnResource(aergoApi);
- }
- }
- protected Pair<ContractTxHash, ContractFunction> find(
- final String encodedContractTxHash, final String functionName) {
- logger.trace("Encoded tx hash: {}", encodedContractTxHash);
- final Base58 encoded = new SimpleBase58(encodedContractTxHash);
- final ContractTxHash contractTxHash = new ContractTxHash(encoded);
- final DeploymentResult deploymentResult =
- encodedContractTxHash2contractAddresses.get(encodedContractTxHash);
- final ContractInterface contractInterface = deploymentResult.getContractInterface();
- final ContractFunction contractFunction = contractInterface.findFunction(functionName)
- .orElseThrow(() -> new ResourceNotFoundException("No " + functionName + " function."));
- return new Pair<>(contractTxHash, contractFunction);
- }
- /**
- * Execute smart contract.
- *
- * @param encodedContractTxHash contract transaction hash
- * @param functionName function's name to execute
- * @param args function's arguments to execute
- *
- * @return execution result
- */
- public ExecutionResult tryExecute(final String encodedContractTxHash, final String functionName,
- final String... args) {
- logger.trace("Encoded tx hash: {}", encodedContractTxHash);
- final Pair<ContractTxHash, ContractFunction> pair = find(encodedContractTxHash, functionName);
- return execute(pair.v1, pair.v2, args);
- }
- protected ExecutionResult execute(
- final ContractTxHash contractTxHash,
- final ContractFunction contractFunction,
- final String... args) {
- ensureAccount();
- final AergoApi aergoApi = aergoPool.borrowResource();
- try {
- final AccountOperation accountOperation = aergoApi.getAccountOperation();
- final ContractOperation contractOperation = aergoApi.getContractOperation();
- final ContractTxReceipt contractTxReceipt =
- contractOperation.getReceipt(contractTxHash);
- logger.debug("Receipt: {}", contractTxReceipt);
- final AccountState syncedAccount = accountOperation.getState(account);
- final ContractAddress contractAddress = contractTxReceipt.getContractAddress();
- logger.trace("Executing...");
- final ContractInvocation contractCall =
- new ContractInvocation(contractAddress, contractFunction, stream(args).toArray());
- account.setNonce(syncedAccount.getNonce() + 1);
- final ContractTxHash executionContractHash = contractOperation.execute(
- account,
- contractCall,
- fee
- );
- final ExecutionResult executionResult = new ExecutionResult();
- executionResult.setContractTransactionHash(executionContractHash.toString());
- return executionResult;
- } finally {
- aergoPool.returnResource(aergoApi);
- }
- }
- /**
- * Query smart contract.
- *
- * @param encodedContractTxHash contract transaction hash
- * @param functionName function's name to execute
- * @param args function's arguments to execute
- *
- * @return query result
- */
- public QueryResult tryQuery(final String encodedContractTxHash, final String functionName,
- final String... args) {
- logger.trace("Encoded tx hash: {}", encodedContractTxHash);
- final Pair<ContractTxHash, ContractFunction> pair = find(encodedContractTxHash, functionName);
- return query(pair.v1, pair.v2, args);
- }
- protected QueryResult query(
- final ContractTxHash contractTxHash,
- final ContractFunction contractFunction,
- final String... args) {
- ensureAccount();
- final AergoApi aergoApi = aergoPool.borrowResource();
- try {
- final ContractOperation contractOperation = aergoApi.getContractOperation();
- final ContractTxReceipt contractTxReceipt =
- contractOperation.getReceipt(contractTxHash);
- logger.debug("Receipt: {}", contractTxReceipt);
- final ContractAddress contractAddress = contractTxReceipt.getContractAddress();
- final ContractInvocation contractCall =
- new ContractInvocation(contractAddress, contractFunction, stream(args).toArray());
- logger.trace("Querying...");
- final ContractResult contractResult = contractOperation.query(contractCall);
- final String resultString = new String(contractResult.getResultInRawBytes().getValue());
- return new QueryResult(resultString);
- } finally {
- aergoPool.returnResource(aergoApi);
- }
- }
- /**
- * Get latest contract.
- *
- * @return latest deployed contract
- */
- public DeploymentResult getLatestContractInformation() {
- if (deployHistory.isEmpty()) {
- throw new ResourceNotFoundException("No deployment!! Deploy your contract first.");
- }
- final DeploymentResult latest = deployHistory.get(deployHistory.size() - 1);
- logger.debug("Latest deployment: {}", latest);
- if (null == latest.getContractInterface()) {
- final String encodedContractTxHash = latest.getEncodedContractTransactionHash();
- logger.trace("Encoded tx hash: {}", encodedContractTxHash);
- final Base58 base58 = new SimpleBase58(encodedContractTxHash);
- final ContractTxHash contractTxHash = new ContractTxHash(base58);
- final ContractInterface contractInterface = getInterface(contractTxHash);
- latest.setContractInterface(contractInterface);
- }
- return latest;
- }
- /**
- * Get application blockchain interface for {@code encodedContractTransactionHash}
- * from {@code endpoint}.
- *
- * @param contractTxHash contract's transaction hash
- *
- * @return abi set
- */
- public ContractInterface getInterface(final ContractTxHash contractTxHash) {
- final AergoApi aergoApi = aergoPool.borrowResource();
- try {
- final ContractOperation contractOperation = aergoApi.getContractOperation();
- ContractTxReceipt receipt = contractOperation.getReceipt(contractTxHash);
- final ContractAddress address = receipt.getContractAddress();
- final ContractInterface contractInterface = contractOperation.getContractInterface(address);
- if (null == contractInterface) {
- throw new ResourceNotFoundException(bind(NL_0, contractTxHash));
- }
- return contractInterface;
- } finally {
- aergoPool.returnResource(aergoApi);
- }
- }
- }