Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transactional Cross-Chain Smart Contract Invocations #22

Merged
merged 14 commits into from
Sep 29, 2024
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
package blockchains.iaas.uni.stuttgart.de;

import blockchains.iaas.uni.stuttgart.de.management.BlockchainPluginManager;

import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

import java.util.List;

import static blockchains.iaas.uni.stuttgart.de.Constants.PF4J_AUTOLOAD_PROPERTY;

@SpringBootApplication
@Log4j2
Expand All @@ -21,19 +11,4 @@ public class BlockchainAccessLayerApplication {
public static void main(String[] args) {
SpringApplication.run(BlockchainAccessLayerApplication.class, args);
}

@Bean
ApplicationRunner loadPlugins(BlockchainPluginManager blockchainPluginManager, @Value("${" + PF4J_AUTOLOAD_PROPERTY + ":false}") String strConf) {
return args -> {
boolean enablePlugins = Boolean.parseBoolean(strConf);
log.debug("{}={}", PF4J_AUTOLOAD_PROPERTY, enablePlugins);

if (enablePlugins) {
log.info("pf4j.autoLoadPlugins=true -> attempting to enable blockchain adapter plugins");
blockchainPluginManager.startPlugins();
}
};

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (c) 2019-2022 Institute for the Architecture of Application System -
* Copyright (c) 2019-2024 Institute for the Architecture of Application System -
* University of Stuttgart
* Author: Ghareeb Falazi
* Co-author: Akshay Patel
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019-2024 Institute for the Architecture of Application System - University of Stuttgart
* Copyright (c) 2019 Institute for the Architecture of Application System - University of Stuttgart
* Author: Ghareeb Falazi
*
* This program and the accompanying materials are made available under the
Expand All @@ -12,12 +12,15 @@
package blockchains.iaas.uni.stuttgart.de.jsonrpc;

import java.util.List;
import java.util.UUID;

import blockchains.iaas.uni.stuttgart.de.api.exceptions.InvalidScipParameterException;
import blockchains.iaas.uni.stuttgart.de.management.BlockchainManager;
import blockchains.iaas.uni.stuttgart.de.api.model.Parameter;
import blockchains.iaas.uni.stuttgart.de.api.model.QueryResult;
import blockchains.iaas.uni.stuttgart.de.api.model.TimeFrame;
import blockchains.iaas.uni.stuttgart.de.management.tccsci.DistributedTransactionManager;
import blockchains.iaas.uni.stuttgart.de.management.tccsci.DistributedTransactionRepository;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
Expand All @@ -32,12 +35,15 @@ public class BalService {
private final String blockchainId;
private final String smartContractPath;
private final BlockchainManager manager;
private final DistributedTransactionManager dtxManager;
private static final String DTX_ID_FIELD_NAME = "dtx_id";

public BalService(String blockchainType, String blockchainId, String smartContractPath, BlockchainManager manager) {
public BalService(String blockchainType, String blockchainId, String smartContractPath, BlockchainManager manager, DistributedTransactionManager dtxManager) {
this.blockchainType = blockchainType;
this.blockchainId = blockchainId;
this.smartContractPath = smartContractPath;
this.manager = manager;
this.dtxManager = dtxManager;
}

@JsonRpcMethod
Expand All @@ -52,9 +58,13 @@ public String Invoke(
@JsonRpcParam("signature") String signature
) {
log.info("SCIP Invoke method is executed!");
manager.invokeSmartContractFunction(blockchainId, smartContractPath, functionIdentifier, inputs, outputs,
requiredConfidence, callbackUrl, timeoutMillis, correlationId, signature);

if (inputs.stream().anyMatch(p -> p.getName().equals(DTX_ID_FIELD_NAME))) {
dtxManager.invokeSc(blockchainId, smartContractPath, functionIdentifier, inputs, outputs,
requiredConfidence, callbackUrl, timeoutMillis, correlationId, signature);
} else {
manager.invokeSmartContractFunction(blockchainId, smartContractPath, functionIdentifier, inputs, outputs,
requiredConfidence, callbackUrl, timeoutMillis, correlationId, signature);
}
return "OK";
}

Expand Down Expand Up @@ -94,7 +104,6 @@ public String Unsubscribe(@JsonRpcOptional @JsonRpcParam("functionIdentifier") S
throw new InvalidScipParameterException();
}


if (!Strings.isNullOrEmpty(functionIdentifier)) {
manager.cancelFunctionSubscriptions(blockchainId, smartContractPath, correlationId, functionIdentifier, parameters);
} else {
Expand Down Expand Up @@ -123,4 +132,29 @@ public QueryResult Query(

throw new InvalidScipParameterException();
}

@JsonRpcMethod
public String Start_Dtx() {
log.info("SCIP-T Start_Dtx method is executed!");

return dtxManager.startDtx().toString();
}

@JsonRpcMethod
public String Commit_Dtx(@JsonRpcParam(DTX_ID_FIELD_NAME) String dtxId) {
log.info("SCIP-T Commit_Dtx method is executed!");
UUID uuid = UUID.fromString(dtxId);
dtxManager.commitDtx(uuid);

return "OK";
}

@JsonRpcMethod
public String Abort_Dtx(@JsonRpcParam(DTX_ID_FIELD_NAME) String dtxId) {
log.info("SCIP-T Abort_Dtx method is executed!");
UUID uuid = UUID.fromString(dtxId);
dtxManager.abortDtx(uuid);

return "OK";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import blockchains.iaas.uni.stuttgart.de.adaptation.AdapterManager;
import blockchains.iaas.uni.stuttgart.de.api.exceptions.*;
import blockchains.iaas.uni.stuttgart.de.api.model.*;
import blockchains.iaas.uni.stuttgart.de.management.callback.CallbackManager;
import blockchains.iaas.uni.stuttgart.de.management.callback.CamundaMessageTranslator;
import blockchains.iaas.uni.stuttgart.de.management.callback.ScipMessageTranslator;
Expand All @@ -31,12 +32,8 @@
import blockchains.iaas.uni.stuttgart.de.management.model.Subscription;
import blockchains.iaas.uni.stuttgart.de.management.model.SubscriptionKey;
import blockchains.iaas.uni.stuttgart.de.management.model.SubscriptionType;
import blockchains.iaas.uni.stuttgart.de.api.model.Parameter;
import blockchains.iaas.uni.stuttgart.de.api.model.QueryResult;
import blockchains.iaas.uni.stuttgart.de.api.model.TimeFrame;
import blockchains.iaas.uni.stuttgart.de.api.model.Transaction;
import blockchains.iaas.uni.stuttgart.de.api.model.TransactionState;
import com.google.common.base.Strings;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -365,41 +362,33 @@ public void invokeSmartContractFunction(
final String correlationId,
final String signature) throws BalException {

// Validate scip parameters!
if (Strings.isNullOrEmpty(blockchainIdentifier)
|| Strings.isNullOrEmpty(smartContractPath)
|| Strings.isNullOrEmpty(functionIdentifier)
|| timeoutMillis < 0
|| MathUtils.doubleCompare(requiredConfidence, 0.0) < 0
|| MathUtils.doubleCompare(requiredConfidence, 100.0) > 0) {
throw new InvalidScipParameterException();
}

final double minimumConfidenceAsProbability = requiredConfidence / 100.0;
final BlockchainAdapter adapter = adapterManager.getAdapter(blockchainIdentifier);
final CompletableFuture<Transaction> future = adapter.invokeSmartContract(smartContractPath,
functionIdentifier, inputs, outputs, minimumConfidenceAsProbability, timeoutMillis);
final CompletableFuture<Transaction> future = this.invokeSmartContractFunction(blockchainIdentifier, smartContractPath,
functionIdentifier, inputs, outputs, requiredConfidence, timeoutMillis, signature);

future.
thenAccept(tx -> {
if (tx != null) {
if (tx.getState() == TransactionState.CONFIRMED || tx.getState() == TransactionState.RETURN_VALUE) {
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getInvocationResponseMessage(
correlationId,
tx.getReturnValues()));
} else {// it is NOT_FOUND (it was dropped from the system due to invalidation) or ERRORED
if (tx.getState() == TransactionState.NOT_FOUND) {
if (callbackUrl != null) {
if (tx.getState() == TransactionState.CONFIRMED || tx.getState() == TransactionState.RETURN_VALUE) {
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getAsynchronousErrorResponseMessage(
ScipMessageTranslator.getInvocationResponseMessage(
correlationId,
new TransactionNotFoundException("The transaction associated with a function invocation is invalidated after it was mined.")));
} else {
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getAsynchronousErrorResponseMessage(
correlationId,
new InvokeSmartContractFunctionFailure("The smart contract function invocation reported an error.")));
tx.getReturnValues()));
} else {// it is NOT_FOUND (it was dropped from the system due to invalidation) or ERRORED
if (tx.getState() == TransactionState.NOT_FOUND) {
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getAsynchronousErrorResponseMessage(
correlationId,
new TransactionNotFoundException("The transaction associated with a function invocation is invalidated after it was mined.")));
} else {
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getAsynchronousErrorResponseMessage(
correlationId,
new InvokeSmartContractFunctionFailure("The smart contract function invocation reported an error.")));
}
}
} else {
log.info("callbackUrl is null");
}
} else {
log.info("Resulting transaction is null");
Expand All @@ -412,6 +401,9 @@ public void invokeSmartContractFunction(
CallbackManager.getInstance().sendCallback(callbackUrl,
ScipMessageTranslator.getAsynchronousErrorResponseMessage(correlationId, (BalException) e.getCause()));

if (e instanceof ManualUnsubscriptionException || e.getCause() instanceof ManualUnsubscriptionException) {
log.info("Manual unsubscription of SC invocation!");
}
// ManualUnsubscriptionException is also captured here
return null;
}).
Expand All @@ -425,6 +417,32 @@ public void invokeSmartContractFunction(
SubscriptionManager.getInstance().createSubscription(correlationId, blockchainIdentifier, smartContractPath, subscription);
}

public CompletableFuture<Transaction> invokeSmartContractFunction(
final String blockchainIdentifier,
final String smartContractPath,
final String functionIdentifier,
final List<Parameter> inputs,
final List<Parameter> outputs,
final double requiredConfidence,
final long timeoutMillis,
final String signature) throws BalException {

// Validate scip parameters!
if (Strings.isNullOrEmpty(blockchainIdentifier)
|| Strings.isNullOrEmpty(smartContractPath)
|| Strings.isNullOrEmpty(functionIdentifier)
|| timeoutMillis < 0
|| MathUtils.doubleCompare(requiredConfidence, 0.0) < 0
|| MathUtils.doubleCompare(requiredConfidence, 100.0) > 0) {
throw new InvalidScipParameterException();
}

final double minimumConfidenceAsProbability = requiredConfidence / 100.0;
final BlockchainAdapter adapter = adapterManager.getAdapter(blockchainIdentifier);
return adapter.invokeSmartContract(smartContractPath,
functionIdentifier, inputs, outputs, minimumConfidenceAsProbability, timeoutMillis);
}

public void subscribeToEvent(
final String blockchainIdentifier,
final String smartContractPath,
Expand All @@ -435,21 +453,13 @@ public void subscribeToEvent(
final String callbackUrl,
final String correlationIdentifier) {

// Validate scip parameters!
if (Strings.isNullOrEmpty(blockchainIdentifier)
|| Strings.isNullOrEmpty(smartContractPath)
|| Strings.isNullOrEmpty(eventIdentifier)
|| MathUtils.doubleCompare(degreeOfConfidence, 0.0) < 0
|| MathUtils.doubleCompare(degreeOfConfidence, 100.0) > 0) {
throw new InvalidScipParameterException();
}

final double minimumConfidenceAsProbability = degreeOfConfidence / 100.0;

// first, we cancel previous identical subscriptions.
this.cancelEventSubscriptions(blockchainIdentifier, smartContractPath, correlationIdentifier, eventIdentifier, outputParameters);
Disposable result = this.adapterManager.getAdapter(blockchainIdentifier)
.subscribeToEvent(smartContractPath, eventIdentifier, outputParameters, minimumConfidenceAsProbability, filter)


Disposable result = this.subscribeToEvent(blockchainIdentifier, smartContractPath, eventIdentifier, outputParameters, degreeOfConfidence, filter)
.doFinally(() -> {
// remove subscription from subscription list
SubscriptionManager.getInstance().removeSubscription(correlationIdentifier, blockchainIdentifier, smartContractPath);
Expand All @@ -469,6 +479,28 @@ public void subscribeToEvent(
SubscriptionManager.getInstance().createSubscription(correlationIdentifier, blockchainIdentifier, smartContractPath, subscription);
}

public Observable<Occurrence> subscribeToEvent(String blockchainIdentifier,
final String smartContractPath,
final String eventIdentifier,
final List<Parameter> outputParameters,
final double degreeOfConfidence,
final String filter) {
// Validate scip parameters!
if (Strings.isNullOrEmpty(blockchainIdentifier)
|| Strings.isNullOrEmpty(smartContractPath)
|| Strings.isNullOrEmpty(eventIdentifier)
|| MathUtils.doubleCompare(degreeOfConfidence, 0.0) < 0
|| MathUtils.doubleCompare(degreeOfConfidence, 100.0) > 0) {
throw new InvalidScipParameterException();
}

final double minimumConfidenceAsProbability = degreeOfConfidence / 100.0;

return this.adapterManager.getAdapter(blockchainIdentifier)
.subscribeToEvent(smartContractPath, eventIdentifier, outputParameters, minimumConfidenceAsProbability, filter);
}


public void cancelEventSubscriptions(String blockchainId, String smartContractId, String correlationId, String eventIdentifier, List<Parameter> parameters) {
// Validate scip parameters!
if (Strings.isNullOrEmpty(blockchainId) || Strings.isNullOrEmpty(smartContractId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,28 @@
import java.nio.file.Paths;
import java.util.List;

import static blockchains.iaas.uni.stuttgart.de.Constants.PF4J_AUTOLOAD_PROPERTY;

@Log4j2
@Component
public class BlockchainPluginManager {
private PluginManager pluginManager = null;
private final String pluginDirStr;
private static final String DEFAULT_PLUGIN_DIR = Paths.get(System.getProperty("user.home"), ".bal").toString();

private BlockchainPluginManager(@Value("${" + Constants.PF4J_PLUGIN_DIR_PROPERTY + "}")
String pluginDir) {
log.info("Initializing Blockchain Plugin Manager: pluginDir={}.", pluginDir);
this.pluginDirStr = pluginDir;
Path[] dirPaths = new Path[0];
Path pluginDirPath = getPluginsPath();
private BlockchainPluginManager(@Value("${" + Constants.PF4J_PLUGIN_DIR_PROPERTY + ":}")
String pluginDir, @Value("${" + PF4J_AUTOLOAD_PROPERTY + ":false}") String strConf) {
log.info("Initializing Blockchain Plugin Manager: pluginDir={}, autoLoadPlugins={}.", pluginDir, strConf);

if (pluginDirPath != null) {
dirPaths = new Path[]{pluginDirPath};
if (pluginDir == null || pluginDir.trim().isEmpty()) {
log.info("No plugin directory is provided. Using default directory instead: {}", DEFAULT_PLUGIN_DIR);
pluginDir = DEFAULT_PLUGIN_DIR;
}

this.pluginManager = new DefaultPluginManager(dirPaths) {
this.pluginDirStr = pluginDir;
Path pluginDirPath = getPluginsPath();

this.pluginManager = new DefaultPluginManager(pluginDirPath) {
//
@Override
protected PluginLoader createPluginLoader() {
Expand All @@ -56,10 +60,10 @@ protected PluginDescriptorFinder createPluginDescriptorFinder() {
}
};

if (pluginDirPath == null) {
log.info("Plugin directory not specified. Not loading plugins at startup.");
if (pluginDirPath == null || !Boolean.parseBoolean(strConf)) {
log.info("Plugin directory not specified or auto loading is disabled. Not loading plugins at startup.");
} else {
log.info("Attempting to load blockchain adapter plugins from: '{}'...", () -> pluginDirPath);
log.info("Attempting to load blockchain adapter plugins from: '{}'", () -> pluginDirPath);
pluginManager.loadPlugins();
startPlugins();
}
Expand Down
Loading
Loading