All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bbva.hancock.sdk.dlt.ethereum.services.EthereumSmartContractService Maven / Gradle / Ivy

package com.bbva.hancock.sdk.dlt.ethereum.services;

import com.bbva.hancock.sdk.HancockSocket;
import com.bbva.hancock.sdk.config.HancockConfig;
import com.bbva.hancock.sdk.dlt.ethereum.models.EthereumRawTransaction;
import com.bbva.hancock.sdk.dlt.ethereum.models.EthereumTransaction;
import com.bbva.hancock.sdk.dlt.ethereum.models.EthereumTransactionAdaptResponse;
import com.bbva.hancock.sdk.dlt.ethereum.models.smartContracts.*;
import com.bbva.hancock.sdk.dlt.ethereum.models.transaction.EthereumSignedTransactionRequest;
import com.bbva.hancock.sdk.dlt.ethereum.models.transaction.EthereumTransactionResponse;
import com.bbva.hancock.sdk.exception.HancockErrorEnum;
import com.bbva.hancock.sdk.exception.HancockException;
import com.bbva.hancock.sdk.exception.HancockTypeErrorEnum;
import com.bbva.hancock.sdk.models.TransactionConfig;
import com.bbva.hancock.sdk.models.socket.HancockSocketTransactionBody;
import com.bbva.hancock.sdk.models.socket.HancockSocketTransactionEvent;
import com.bbva.hancock.sdk.util.ValidateParameters;
import com.google.gson.Gson;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.protocol.core.methods.response.AbiDefinition;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;

import static com.bbva.hancock.sdk.Common.*;
import static com.bbva.hancock.sdk.models.socket.HancockSocketMessageResponseKind.SMARTCONTRACTDEPLOYMENT;

public class EthereumSmartContractService {

    private static final Logger LOGGER = LoggerFactory.getLogger(EthereumSmartContractService.class);
    private static final MediaType CONTENT_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");

    private final EthereumTransactionService transactionClient;

    private final HancockConfig config;
    private final static Gson gson = new Gson();

    public EthereumSmartContractService(final HancockConfig config, final EthereumTransactionService transactionClient) {
        this.config = config;
        this.transactionClient = transactionClient;
    }

    /**
     * Makes an invocation to an smart contract method.
     * Invocations are used to call smart contract methods that writes information in the blockchain consuming gas
     *
     * @param contractAddressOrAlias Address or alias of the smart contract registered in Hancock
     * @param method                 The name of the method to call
     * @param params                 An array of arguments passed to the method
     * @param from                   The address of the account doing the call
     * @param options                Configuration of how the transaction will be send to the network
     * @return The returned value from the smart contract method
     * @throws Exception
     */
    public EthereumTransactionResponse invoke(final String contractAddressOrAlias, final String method, final ArrayList params, final String from, final TransactionConfig options) throws Exception {

        if (options.getPrivateKey() == null && options.getProvider() == null) {
            throw new HancockException(HancockTypeErrorEnum.ERROR_INTERNAL, "50007", 500, HancockErrorEnum.ERROR_NOKEY_NOPROVIDER.getMessage(), HancockErrorEnum.ERROR_NOKEY_NOPROVIDER.getMessage());
        }
        ValidateParameters.checkForContent(contractAddressOrAlias, "Address or Alias");
        ValidateParameters.checkForContent(method, "Method");
        ValidateParameters.checkForContent(from, "From");
        ValidateParameters.checkAddress(from);

        final EthereumTransactionAdaptResponse invokeResponse = adaptInvoke(contractAddressOrAlias, method, params, from);
        return transactionClient.send(invokeResponse.getData(), options);
    }

    /**
     * Makes an invocation to an smart contract method with a specific raw abi.
     * Invocations are used to call smart contract methods that writes information in the blockchain consuming gas
     *
     * @param contractAddressOrAlias Address or alias of the smart contract registered in Hancock
     * @param method                 The name of the method to call
     * @param params                 An array of arguments passed to the method
     * @param from                   The address of the account doing the call
     * @param options                Configuration of how the transaction will be send to the network
     * @param abi                    raw in json format
     * @return The returned value from the smart contract method
     * @throws Exception
     */
    public EthereumTransactionResponse invokeAbi(final String contractAddressOrAlias, final String method, final ArrayList params, final String from, final TransactionConfig options, final ArrayList abi) throws Exception {

        if (options.getPrivateKey() == null && options.getProvider() == null) {
            throw new HancockException(HancockTypeErrorEnum.ERROR_INTERNAL, "50007", 500, HancockErrorEnum.ERROR_NOKEY_NOPROVIDER.getMessage(), HancockErrorEnum.ERROR_NOKEY_NOPROVIDER.getMessage());
        }
        ValidateParameters.checkForContent(contractAddressOrAlias, "Address or Alias");
        ValidateParameters.checkForContent(method, "Method");
        ValidateParameters.checkForContent(from, "From");
        ValidateParameters.checkAddress(from);

        final String action = "send";
        final Response response = adaptInvokeAbi(contractAddressOrAlias, method, params, from, action, abi);
        final EthereumTransactionAdaptResponse responseModel = checkStatus(response, EthereumTransactionAdaptResponse.class);

        return transactionClient.send(responseModel.getData(), options);
    }

    /**
     * Makes a call to an smart contract method. Calls only fetch information from blockchain so it doesn't consume gas
     *
     * @param contractAddressOrAlias Address or alias of the smart contract registered in Hancock
     * @param method                 The name of the method to call
     * @param params                 An array of arguments passed to the method
     * @param from                   The address of the account doing the call
     * @return The returned value from the smart contract method
     * @throws HancockException
     */
    public EthereumCallResponse call(final String contractAddressOrAlias, final String method, final ArrayList params, final String from) throws HancockException {

        ValidateParameters.checkForContent(contractAddressOrAlias, "Address or Alias");
        ValidateParameters.checkForContent(method, "Method");
        ValidateParameters.checkForContent(from, "From");
        ValidateParameters.checkAddress(from);

        final String url = generateUri(config.getAdapter().getHost(), config.getAdapter().getPort(), config.getAdapter().getBase(),
                config.getAdapter().getResources().get("invoke").replaceAll("__ADDRESS_OR_ALIAS__", contractAddressOrAlias));

        final EthereumAdaptInvokeRequest callBody = new EthereumAdaptInvokeRequest(method, from, params, "call");
        final Response response = createRequestAndMakeCall(url, callBody);
        return checkStatus(response, EthereumCallResponse.class);

    }

    /**
     * Makes a call to an smart contract method. Calls only fetch information from blockchain so it doesn't consume gas
     *
     * @param contractAddressOrAlias Address or alias of the smart contract registered in Hancock
     * @param method                 The name of the method to call
     * @param params                 An array of arguments passed to the method
     * @param from                   The address of the account doing the call
     * @param abi                    raw in json format
     * @return The returned value from the smart contract method
     * @throws HancockException
     */
    public EthereumCallResponse callAbi(final String contractAddressOrAlias, final String method, final ArrayList params, final String from, final ArrayList abi) throws HancockException {

        ValidateParameters.checkForContent(contractAddressOrAlias, "Address or Alias");
        ValidateParameters.checkForContent(method, "Method");
        ValidateParameters.checkForContent(from, "From");
        ValidateParameters.checkAddress(from);

        final String action = "call";
        final Response response = adaptInvokeAbi(contractAddressOrAlias, method, params, from, action, abi);
        return checkStatus(response, EthereumCallResponse.class);

    }

    //TODO register: how to pass the abi??

    /**
     * Register a new smart contract instance in Hancock
     *
     * @param alias   An alias for the smart contract
     * @param address The address of the deployed smart contract instance
     * @param abi     The application binary interface (abi) of the deployed smart contract
     * @return The result of the request
     * @throws HancockException
     */
    public EthereumRegisterResponse register(final String alias, final String address, final ArrayList abi) throws HancockException {

        ValidateParameters.checkForContent(alias, "Alias");
        ValidateParameters.checkForContent(address, "Address");
        ValidateParameters.checkAddress(address);

        final String url = generateUri(config.getAdapter().getHost(), config.getAdapter().getPort(), config.getAdapter().getBase(),
                config.getAdapter().getResources().get("register"));

        final EthereumRegisterRequest registerBody = new EthereumRegisterRequest(address, alias, abi);

        final Response response = createRequestAndMakeCall(url, registerBody);
        return checkStatus(response, EthereumRegisterResponse.class);

    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-event" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToEvents(final List contracts) throws HancockException {
        return subscribeToEvents(contracts, "", null);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-event" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param consumer  A consumer plugin previously configured in hancock that will handle each received event
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToEvents(final List contracts, final String consumer) throws HancockException {
        return subscribeToEvents(contracts, consumer, null);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-event" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param callback  A function to be called when the sockets has the connection ready. Has the socket as a param
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToEvents(final List contracts, final Function callback) throws HancockException {
        return subscribeToEvents(contracts, "", callback);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-event" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param consumer  A consumer plugin previously configured in hancock that will handle each received event
     * @param callback  A function to be called when the sockets has the connection ready. Has the socket as a param
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToEvents(final List contracts, final String consumer, final Function callback) throws HancockException {
        final String url = generateUri(config.getBroker().getHost(), config.getBroker().getPort(), config.getBroker().getBase(),
                config.getBroker().getResources().get("events")
                        .replaceAll("__ADDRESS__", "")
                        .replaceAll("__SENDER__", "")
                        .replaceAll("__CONSUMER__", consumer));

        try {
            final HancockSocket socket = new HancockSocket(url);
            socket.on("ready", o -> {
                socket.watchContractEvent(contracts);
                if (callback != null) {
                    callback.apply(socket);
                }
                return null;
            });
            return socket;
        } catch (final HancockException e) {
            LOGGER.error(e.getMessage());
            throw e;
        }
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-transaction" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToTransactions(final List contracts) throws HancockException {
        return subscribeToTransactions(contracts, "", null);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-transaction" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param consumer  A consumer plugin previously configured in hancock that will handle each received event
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToTransactions(final List contracts, final String consumer) throws HancockException {
        return subscribeToTransactions(contracts, consumer, null);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-transaction" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param callback  A function to be called when the sockets has the connection ready. Has the socket as a param
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToTransactions(final List contracts, final Function callback) throws HancockException {
        return subscribeToTransactions(contracts, "", callback);
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-transaction" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param consumer  A consumer plugin previously configured in hancock that will handle each received event
     * @param callback  A function to be called when the sockets has the connection ready. Has the socket as a param
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToTransactions(final List contracts, final String consumer, final Function callback) throws HancockException {
        final String url = generateUri(config.getBroker().getHost(), config.getBroker().getPort(), config.getBroker().getBase(),
                config.getBroker().getResources().get("events")
                        .replaceAll("__ADDRESS__", "")
                        .replaceAll("__SENDER__", "")
                        .replaceAll("__CONSUMER__", consumer));

        try {
            final HancockSocket socket = new HancockSocket(url);
            socket.on("ready", o -> {
                socket.watchContractTransaction(contracts);
                if (callback != null) {
                    callback.apply(socket);
                }
                return null;
            });
            return socket;
        } catch (final HancockException e) {
            LOGGER.error(e.getMessage());
            throw e;
        }
    }

    /**
     * Create a websocket subscription to watch transactions of type "contract-transaction" in the network
     *
     * @param contracts An array of address or alias that will be added to the watch list
     * @param consumer  A consumer plugin previously configured in hancock that will handle each received event
     * @param callback  A function to be called when the sockets has the connection ready. Has the socket as a param
     * @return A HancockSocket object which can add new subscriptions and listen incoming message
     * @throws HancockException
     */
    public HancockSocket subscribeToContractsDeployments(final List contracts, final String consumer, final Function callback) throws HancockException {
        final String url = generateUri(config.getBroker().getHost(), config.getBroker().getPort(), config.getBroker().getBase(),
                config.getBroker().getResources().get("events")
                        .replaceAll("__ADDRESS__", "")
                        .replaceAll("__SENDER__", "")
                        .replaceAll("__CONSUMER__", consumer));

        try {
            final HancockSocket socket = new HancockSocket(url);
            socket.on("ready", o -> {
                socket.watchContractDeployments(contracts);
                if (callback != null) {
                    callback.apply(socket);
                }
                return null;
            });
            return socket;
        } catch (final HancockException e) {
            LOGGER.error(e.getMessage());
            throw e;
        }
    }

    protected EthereumTransactionAdaptResponse adaptInvoke(final String contractAddressOrAlias, final String method, final ArrayList params, final String from) throws HancockException {

        final String url = generateUri(config.getAdapter().getHost(), config.getAdapter().getPort(), config.getAdapter().getBase(),
                config.getAdapter().getResources().get("invoke").replaceAll("__ADDRESS_OR_ALIAS__", contractAddressOrAlias));

        final EthereumAdaptInvokeRequest invokebody = new EthereumAdaptInvokeRequest(method, from, params, "send");

        final Response response = createRequestAndMakeCall(url, invokebody);
        return checkStatus(response, EthereumTransactionAdaptResponse.class);

    }

    protected Response adaptInvokeAbi(final String to, final String method, final ArrayList params, final String from, final String action, final ArrayList abi) throws HancockException {

        final String url = generateUri(config.getAdapter().getHost(), config.getAdapter().getPort(), config.getAdapter().getBase(),
                config.getAdapter().getResources().get("invokeAbi"));

        final EthereumAdaptInvokeAbiRequest invokeabibody = new EthereumAdaptInvokeAbiRequest(method, from, params, action, to, abi);

        final String json = gson.toJson(invokeabibody);
        final RequestBody body = RequestBody.create(CONTENT_TYPE_JSON, json);

        final Request request = getRequest(url, body);

        return makeCall(request);

    }

    private String generateUri(final String host, final int port, final String base, final String path) {
        final StringBuilder url = new StringBuilder(host);
        url.append(":");
        url.append(port);
        url.append(base);
        url.append(path);
        return url.toString();
    }


    private final Map deploySocketResponse = new HashMap<>();
    private final Map keyTransactions = new HashMap<>();

    /**
     * Deploy a new smart contract instance
     *
     * @param from              The sender of the operation
     * @param urlBase           An url for the deploy
     * @param constructorName   An method to invoke
     * @param constructorParams The params of the call
     * @param options           The transaction config
     * @return The result of the request
     * @throws HancockException
     */
    public Future deploy(final String from, final String urlBase, final String constructorName, final List constructorParams, final TransactionConfig options) throws Exception {

        final String key = String.valueOf(new Date().getTime());
        final ExecutorService executor = Executors.newSingleThreadExecutor();

        return executor.submit(() -> {
            //1. Open broker connection to receive messages
            final HancockSocket socket = subscribeToContractsDeployments(Collections.singletonList(from), "", msg -> checkDeployEvents((HancockSocket) msg, key));

            //2. Call to adapter deploy
            final String url = generateUri(config.getAdapter().getHost(), config.getAdapter().getPort(), config.getAdapter().getBase(),
                    config.getAdapter().getResources().get("deploy"));

            final EthereumDeployRequest deployBody = new EthereumDeployRequest(urlBase, constructorName, constructorParams, from);

            final Response response = createRequestAndMakeCall(url, deployBody);

            final EthereumTransactionAdaptResponse ethereumResponse = checkStatus(response, EthereumTransactionAdaptResponse.class);

            //3. Sign and send
            final EthereumDeploySendResponse deployResponse = send(ethereumResponse.getData(), options);
            keyTransactions.put(key, deployResponse.getTransactionHash());
            //4. Wait response en return
            do {
                Thread.sleep(300);
            } while (!deploySocketResponse.containsKey(deployResponse.getTransactionHash()));

            socket.removeAllListeners();
            socket.getWs().close();
            final HancockSocketTransactionBody hancockSocketTransactionBody = deploySocketResponse.get(deployResponse.getTransactionHash());
            deploySocketResponse.remove(deployResponse.getTransactionHash());
            return hancockSocketTransactionBody;
        });

    }

    private boolean checkDeployEvents(final HancockSocket socket, final String transactionHash) {
        socket.onTransaction(SMARTCONTRACTDEPLOYMENT, msg -> processEvent(msg, transactionHash));
        return true;
    }

    private Void processEvent(final HancockSocketTransactionEvent event, final String key) {
        final String transactionHash = keyTransactions.get(key);
        if (event.getBody().getTransactionId().equals(transactionHash)) {
            deploySocketResponse.put(transactionHash, event.getBody());
            keyTransactions.remove(key);
        }

        return null;
    }

    private EthereumDeploySendResponse send(final EthereumTransaction rawtx, final TransactionConfig txConfig) throws Exception {
        final String signedTransaction = transactionClient.signTransaction(new EthereumRawTransaction(rawtx), txConfig.getPrivateKey());

        final String url = config.getWallet().getHost() + ':' + config.getWallet().getPort() + config.getWallet().getBase() + config.getWallet().getResources().get("sendSignedTx");
        final EthereumSignedTransactionRequest signedTxBody = new EthereumSignedTransactionRequest(signedTransaction);

        final Response response = createRequestAndMakeCall(url, signedTxBody);
        final EthereumDeploySendResponse txResponse = checkStatus(response, EthereumDeploySendResponse.class);
        return txResponse;
    }

    private  Response createRequestAndMakeCall(final String url, final T bodyObj) throws HancockException {
        final String json = gson.toJson(bodyObj);
        final RequestBody body = RequestBody.create(CONTENT_TYPE_JSON, json);
        final Request request = getRequest(url, body);
        return makeCall(request);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy