Skip to content
19 changes: 15 additions & 4 deletions exonum-java-binding/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details.
`ListProof`;
- [`Blockchain`][blockchain-proofs].
- `ProofEntryIndexProxy` collection.
- Transaction precondition utility methods,
see `com.exonum.binding.core.transaction.ExecutionPreconditions`. (#1351)
- Execution preconditions utility methods,
see `com.exonum.binding.core.service.ExecutionPreconditions`. (#1351)
- `supervisor-mode` CLI parameter added for `generate-template` command. It
allows to configure the mode of the Supervisor service. Possible values are
"simple" and "decentralized". (#1361)
Expand All @@ -85,14 +85,25 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details.
`@Transaction(TX_ID)`, instead of classes implementing
`Transaction` _interface_. (#1274, #1307)
- Any exceptions thrown from the `Transaction` methods
but `TransactionExecutionException` are saved with the error kind
but `ExecutionException` are saved with the error kind
"unexpected" into `Blockchain#getCallErrors`.
- Redefined `TransactionExecutionException`:
- Renamed into `ExecutionException`
- Renamed into `ExecutionException` and moved to package
`com.exonum.binding.core.service`
- Made `TransactionExecutionException` an unchecked (runtime) exception
- Specified it as _the_ exception to communicate execution errors
of `Service` methods: `@Transaction`s; `Service#afterTransactions`,
`#initialize`; `Configurable` methods.
- Similarly, redefined `TransactionContext`:
- Renamed into `ExecutionContext` and moved to package
`com.exonum.binding.core.service`
- Replaced `BlockchainData` argument in transaction-like `Service` and
`Configurable` methods with `ExecutionContext`. The `BlockchainData`
remains accessible via `TransactionContext#getBlockchainData`,
and service data via `TransactionContext#getServiceData`.
- Made `getTransactionMessageHash` return `Optional<HashCode`;
and `getAuthorPk` return `Optional<PublicKey>` because the context
is also used for non-transaction methods. (#1462)
- Renamed `Service#beforeCommit` into `Service#afterTransactions`.
- Allowed throwing execution exceptions from `Service#afterTransactions`
(ex. `beforeCommit`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ extern crate java_bindings;
extern crate lazy_static;
extern crate test;

use std::sync::Arc;
use test::{black_box, Bencher};

use integration_tests::vm::create_vm_for_benchmarks_with_classes;
use java_bindings::{
jni::{
Expand All @@ -28,10 +31,7 @@ use java_bindings::{
Executor, JniResult,
};

use std::sync::Arc;
use test::{black_box, Bencher};

const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/transaction/ExecutionException";
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/service/ExecutionException";

lazy_static! {
pub static ref VM: Arc<JavaVM> = create_vm_for_benchmarks_with_classes();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::sync::Arc;

use exonum_derive::ExecutionFail;

use integration_tests::vm::create_vm_for_tests_with_classes;
use java_bindings::{
exonum::{
Expand All @@ -14,16 +17,14 @@ use java_bindings::{
};
use lazy_static::lazy_static;

use std::sync::Arc;

lazy_static! {
static ref VM: Arc<JavaVM> = create_vm_for_tests_with_classes();
pub static ref EXECUTOR: Executor = Executor::new(VM.clone());
}

const ARITHMETIC_EXCEPTION_CLASS: &str = "java/lang/ArithmeticException";
const ILLEGAL_ARGUMENT_EXCEPTION_CLASS: &str = "java/lang/IllegalArgumentException";
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/transaction/ExecutionException";
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/service/ExecutionException";
const UNEXPECTED_EXECUTION_EXCEPTION_CLASS: &str =
"com/exonum/binding/core/runtime/UnexpectedExecutionException";
const STRING_CLASS: &str = "java/lang/String";
Expand Down
11 changes: 4 additions & 7 deletions exonum-java-binding/core/rust/src/utils/jni_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
//!
//! See: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#jni_onload

use std::{os::raw::c_void, panic::catch_unwind};

use jni::{
objects::{GlobalRef, JMethodID},
sys::{jint, JNI_VERSION_1_8},
Expand All @@ -28,8 +30,6 @@ use jni::{
use log::debug;
use parking_lot::Once;

use std::{os::raw::c_void, panic::catch_unwind};

/// Invalid JNI version constant, signifying JNI_OnLoad failure.
const INVALID_JNI_VERSION: jint = 0;
const SERVICE_RUNTIME_ADAPTER_CLASS: &str = "com/exonum/binding/core/runtime/ServiceRuntimeAdapter";
Expand Down Expand Up @@ -97,7 +97,7 @@ unsafe fn cache_methods(env: &JNIEnv) {
);
EXECUTION_EXCEPTION_GET_ERROR_CODE = get_method_id(
&env,
"com/exonum/binding/core/transaction/ExecutionException",
"com/exonum/binding/core/service/ExecutionException",
"getErrorCode",
"()B",
);
Expand Down Expand Up @@ -159,10 +159,7 @@ unsafe fn cache_methods(env: &JNIEnv) {
JAVA_LANG_ERROR = get_class(env, "java/lang/Error");
JAVA_LANG_RUNTIME_EXCEPTION = get_class(env, "java/lang/RuntimeException");
JAVA_LANG_ILLEGAL_ARGUMENT_EXCEPTION = get_class(env, "java/lang/IllegalArgumentException");
EXECUTION_EXCEPTION = get_class(
env,
"com/exonum/binding/core/transaction/ExecutionException",
);
EXECUTION_EXCEPTION = get_class(env, "com/exonum/binding/core/service/ExecutionException");
UNEXPECTED_EXECUTION_EXCEPTION = get_class(
env,
"com/exonum/binding/core/runtime/UnexpectedExecutionException",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.exonum.binding.core.blockchain.proofs.BlockProof;
import com.exonum.binding.core.blockchain.proofs.IndexProof;
import com.exonum.binding.core.service.Configuration;
import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.storage.database.Access;
import com.exonum.binding.core.storage.indices.KeySetIndexProxy;
import com.exonum.binding.core.storage.indices.ListIndex;
Expand Down Expand Up @@ -195,7 +196,7 @@ public BlockProof createBlockProof(long blockHeight) {
* logic, an index may remain uninitialized indefinitely. Therefore, if proofs for an
* empty index need to be created, it must be initialized early in the service lifecycle
* (e.g., in {@link
* com.exonum.binding.core.service.Service#initialize(BlockchainData, Configuration)}.
* com.exonum.binding.core.service.Service#initialize(ExecutionContext, Configuration)}.
* <!-- TODO: Simplify once initialization happens automatically: ECR-4121 -->
*/
public IndexProof createIndexProof(String fullIndexName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.service.BlockCommittedEvent;
import com.exonum.binding.core.service.BlockCommittedEventImpl;
import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.service.ExecutionException;
import com.exonum.binding.core.storage.database.Snapshot;
import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.binding.core.transaction.TransactionContext;
import com.exonum.binding.core.transport.Server;
import com.exonum.messages.core.runtime.Errors.ErrorKind;
import com.exonum.messages.core.runtime.Lifecycle.InstanceStatus;
Expand Down Expand Up @@ -200,7 +200,8 @@ public void initiateAddingService(BlockchainData blockchainData, ServiceInstance
ServiceWrapper service = createServiceInstance(instanceSpec);

// Initialize it
service.initialize(blockchainData, new ServiceConfiguration(configuration));
ExecutionContext context = newContext(service, blockchainData).build();
service.initialize(context, new ServiceConfiguration(configuration));
}

// Log the initialization event
Expand Down Expand Up @@ -234,7 +235,8 @@ public void initiateResumingService(BlockchainData blockchainData,
synchronized (lock) {
checkStoppedService(instanceSpec.getId());
ServiceWrapper service = createServiceInstance(instanceSpec);
service.resume(blockchainData, arguments);
ExecutionContext context = newContext(service, blockchainData).build();
service.resume(context, arguments);
}
logger.info("Resumed service: {}", instanceSpec);
} catch (Exception e) {
Expand Down Expand Up @@ -383,13 +385,9 @@ public void executeTransaction(int serviceId, String interfaceName, int txId,
PublicKey authorPublicKey) {
synchronized (lock) {
ServiceWrapper service = getServiceById(serviceId);
String serviceName = service.getName();
TransactionContext context = TransactionContext.builder()
.blockchainData(blockchainData)
ExecutionContext context = newContext(service, blockchainData)
.txMessageHash(txMessageHash)
.authorPk(authorPublicKey)
.serviceName(serviceName)
.serviceId(serviceId)
.build();
try {
service.executeTransaction(interfaceName, txId, arguments, callerServiceId, context);
Expand All @@ -410,7 +408,8 @@ public void beforeTransactions(int serviceId, BlockchainData blockchainData) {
synchronized (lock) {
ServiceWrapper service = getServiceById(serviceId);
try {
service.beforeTransactions(blockchainData);
ExecutionContext context = newContext(service, blockchainData).build();
service.beforeTransactions(context);
} catch (Exception e) {
logger.error("Service {} threw exception in beforeTransactions.", service.getName(), e);
throw e;
Expand All @@ -435,7 +434,8 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
synchronized (lock) {
ServiceWrapper service = getServiceById(serviceId);
try {
service.afterTransactions(blockchainData);
ExecutionContext context = newContext(service, blockchainData).build();
service.afterTransactions(context);
} catch (Exception e) {
logger.error("Service {} threw exception in afterTransactions."
+ " Any changes will be rolled-back", service.getName(), e);
Expand All @@ -444,6 +444,15 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
}
}

/** Creates a fully-initialized builder of a 'zero' context for the given service. */
private static ExecutionContext.Builder newContext(ServiceWrapper service,
BlockchainData blockchainData) {
return ExecutionContext.builder()
.blockchainData(blockchainData)
.serviceName(service.getName())
.serviceId(service.getId());
}

/**
* Notifies the services in the runtime of the block commit event.
* @param snapshot a snapshot of the current database state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.proxy.Cleaner;
import com.exonum.binding.core.proxy.CloseFailuresException;
import com.exonum.binding.core.service.ExecutionException;
import com.exonum.binding.core.storage.database.Snapshot;
import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.messages.core.runtime.Base.ArtifactId;
import com.exonum.messages.core.runtime.Base.InstanceSpec;
import com.exonum.messages.core.runtime.Lifecycle.InstanceStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@
import static com.google.common.base.Throwables.throwIfInstanceOf;
import static java.lang.String.format;

import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.service.BlockCommittedEvent;
import com.exonum.binding.core.service.Configurable;
import com.exonum.binding.core.service.Configuration;
import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.service.ExecutionException;
import com.exonum.binding.core.service.Node;
import com.exonum.binding.core.service.Service;
import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.binding.core.transaction.TransactionContext;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.UrlEscapers;
import com.google.inject.Inject;
Expand Down Expand Up @@ -102,16 +101,16 @@ int getId() {
return instanceSpec.getId();
}

void initialize(BlockchainData blockchainData, Configuration configuration) {
callServiceMethod(() -> service.initialize(blockchainData, configuration));
void initialize(ExecutionContext context, Configuration configuration) {
callServiceMethod(() -> service.initialize(context, configuration));
}

void resume(BlockchainData blockchainData, byte[] arguments) {
callServiceMethod(() -> service.resume(blockchainData, arguments));
void resume(ExecutionContext context, byte[] arguments) {
callServiceMethod(() -> service.resume(context, arguments));
}

void executeTransaction(String interfaceName, int txId, byte[] arguments, int callerServiceId,
TransactionContext context) {
ExecutionContext context) {
switch (interfaceName) {
case DEFAULT_INTERFACE_NAME: {
executeIntrinsicTransaction(txId, arguments, context);
Expand All @@ -126,12 +125,12 @@ void executeTransaction(String interfaceName, int txId, byte[] arguments, int ca
}
}

private void executeIntrinsicTransaction(int txId, byte[] arguments, TransactionContext context) {
private void executeIntrinsicTransaction(int txId, byte[] arguments, ExecutionContext context) {
invoker.invokeTransaction(txId, arguments, context);
}

private void executeConfigurableTransaction(int txId, byte[] arguments, int callerServiceId,
TransactionContext context) {
ExecutionContext context) {
// Check the service implements Configurable
checkArgument(service instanceof Configurable, "Service (%s) doesn't implement Configurable",
getName());
Expand All @@ -141,27 +140,26 @@ private void executeConfigurableTransaction(int txId, byte[] arguments, int call
callerServiceId, SUPERVISOR_SERVICE_ID);
// Invoke the Configurable operation
Configurable configurable = (Configurable) service;
BlockchainData fork = context.getBlockchainData();
Configuration config = new ServiceConfiguration(arguments);
switch (txId) {
case VERIFY_CONFIGURATION_TX_ID:
callServiceMethod(() -> configurable.verifyConfiguration(fork, config));
callServiceMethod(() -> configurable.verifyConfiguration(context, config));
break;
case APPLY_CONFIGURATION_TX_ID:
callServiceMethod(() -> configurable.applyConfiguration(fork, config));
callServiceMethod(() -> configurable.applyConfiguration(context, config));
break;
default:
throw new IllegalArgumentException(
format("Unknown txId (%d) in Configurable interface", txId));
}
}

void beforeTransactions(BlockchainData blockchainData) {
callServiceMethod(() -> service.beforeTransactions(blockchainData));
void beforeTransactions(ExecutionContext context) {
callServiceMethod(() -> service.beforeTransactions(context));
}

void afterTransactions(BlockchainData blockchainData) {
callServiceMethod(() -> service.afterTransactions(blockchainData));
void afterTransactions(ExecutionContext context) {
callServiceMethod(() -> service.afterTransactions(context));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

import com.exonum.binding.common.serialization.Serializer;
import com.exonum.binding.common.serialization.StandardSerializers;
import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.transaction.Transaction;
import com.exonum.binding.core.transaction.TransactionContext;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.MessageLite;
import java.lang.invoke.MethodHandle;
Expand Down Expand Up @@ -96,7 +96,7 @@ private static void validateTransactionMethod(Method transaction, Class<?> servi
checkArgument(firstParameter == byte[].class || isProtobufArgument(firstParameter),
String.format(errorMessage
+ " But first parameter type was: %s", firstParameter.getName()));
checkArgument(TransactionContext.class.isAssignableFrom(secondParameter),
checkArgument(ExecutionContext.class.isAssignableFrom(secondParameter),
String.format(errorMessage
+ " But second parameter type was: %s", secondParameter.getName()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

import static com.google.common.base.Preconditions.checkArgument;

import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.service.ExecutionException;
import com.exonum.binding.core.service.Service;
import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.binding.core.transaction.TransactionContext;
import com.google.inject.Inject;
import java.util.Map;

Expand Down Expand Up @@ -52,7 +52,7 @@ final class TransactionInvoker {
* @throws UnexpectedExecutionException if any other exception is thrown by
* the transaction method, it is wrapped as cause
*/
void invokeTransaction(int transactionId, byte[] arguments, TransactionContext context) {
void invokeTransaction(int transactionId, byte[] arguments, ExecutionContext context) {
checkArgument(transactionMethods.containsKey(transactionId),
"No method with transaction id (%s)", transactionId);
TransactionMethod transactionMethod = transactionMethods.get(transactionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package com.exonum.binding.core.runtime;

import com.exonum.binding.common.serialization.Serializer;
import com.exonum.binding.core.service.ExecutionContext;
import com.exonum.binding.core.service.ExecutionException;
import com.exonum.binding.core.service.Service;
import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.binding.core.transaction.TransactionContext;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.WrongMethodTypeException;

Expand All @@ -36,7 +36,7 @@ class TransactionMethod {
this.argumentsSerializer = argumentsSerializer;
}

void invoke(Service targetService, byte[] arguments, TransactionContext context) {
void invoke(Service targetService, byte[] arguments, ExecutionContext context) {
Object argumentsObject = serializeArguments(arguments);
try {
methodHandle.invoke(targetService, argumentsObject, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.exonum.binding.core.transaction.ExecutionException;
import com.exonum.binding.core.service.ExecutionException;

/**
* An "unexpected" service execution exception indicates that any exception
Expand Down
Loading