Skip to content

Commit 4e00cd8

Browse files
Unify the context in transaction-like methods: [ECR-4303] (#1462)
Redefine the TransactionContext: rename it to ExecutionContext and move to service package. * Return Optional for optional context properties. Use `TransactionContext` in transaction-like methods: - Service#initialize, #resume, #before- and #afterTransactions - Configurable That enables easier re-use of @transaction methods, as they already accept TransactionContext; and makes the API a little easier to learn because there are fewer immediate abstractions (though you will have to use TransactionContext#getBlockchainData to access the data anyway). * Fix the tutorial code * Reorder code in MyService Also, move ex-transaction classes to service package. * Fix native * Cargo fmt
2 parents cbfb385 + 0e2595d commit 4e00cd8

File tree

41 files changed

+364
-329
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+364
-329
lines changed

exonum-java-binding/CHANGELOG.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details.
5959
`ListProof`;
6060
- [`Blockchain`][blockchain-proofs].
6161
- `ProofEntryIndexProxy` collection.
62-
- Transaction precondition utility methods,
63-
see `com.exonum.binding.core.transaction.ExecutionPreconditions`. (#1351)
62+
- Execution preconditions utility methods,
63+
see `com.exonum.binding.core.service.ExecutionPreconditions`. (#1351)
6464
- `supervisor-mode` CLI parameter added for `generate-template` command. It
6565
allows to configure the mode of the Supervisor service. Possible values are
6666
"simple" and "decentralized". (#1361)
@@ -85,14 +85,25 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details.
8585
`@Transaction(TX_ID)`, instead of classes implementing
8686
`Transaction` _interface_. (#1274, #1307)
8787
- Any exceptions thrown from the `Transaction` methods
88-
but `TransactionExecutionException` are saved with the error kind
88+
but `ExecutionException` are saved with the error kind
8989
"unexpected" into `Blockchain#getCallErrors`.
9090
- Redefined `TransactionExecutionException`:
91-
- Renamed into `ExecutionException`
91+
- Renamed into `ExecutionException` and moved to package
92+
`com.exonum.binding.core.service`
9293
- Made `TransactionExecutionException` an unchecked (runtime) exception
9394
- Specified it as _the_ exception to communicate execution errors
9495
of `Service` methods: `@Transaction`s; `Service#afterTransactions`,
9596
`#initialize`; `Configurable` methods.
97+
- Similarly, redefined `TransactionContext`:
98+
- Renamed into `ExecutionContext` and moved to package
99+
`com.exonum.binding.core.service`
100+
- Replaced `BlockchainData` argument in transaction-like `Service` and
101+
`Configurable` methods with `ExecutionContext`. The `BlockchainData`
102+
remains accessible via `TransactionContext#getBlockchainData`,
103+
and service data via `TransactionContext#getServiceData`.
104+
- Made `getTransactionMessageHash` return `Optional<HashCode`;
105+
and `getAuthorPk` return `Optional<PublicKey>` because the context
106+
is also used for non-transaction methods. (#1462)
96107
- Renamed `Service#beforeCommit` into `Service#afterTransactions`.
97108
- Allowed throwing execution exceptions from `Service#afterTransactions`
98109
(ex. `beforeCommit`).

exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ extern crate java_bindings;
1818
extern crate lazy_static;
1919
extern crate test;
2020

21+
use std::sync::Arc;
22+
use test::{black_box, Bencher};
23+
2124
use integration_tests::vm::create_vm_for_benchmarks_with_classes;
2225
use java_bindings::{
2326
jni::{
@@ -28,10 +31,7 @@ use java_bindings::{
2831
Executor, JniResult,
2932
};
3033

31-
use std::sync::Arc;
32-
use test::{black_box, Bencher};
33-
34-
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/transaction/ExecutionException";
34+
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/service/ExecutionException";
3535

3636
lazy_static! {
3737
pub static ref VM: Arc<JavaVM> = create_vm_for_benchmarks_with_classes();

exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use std::sync::Arc;
2+
13
use exonum_derive::ExecutionFail;
4+
25
use integration_tests::vm::create_vm_for_tests_with_classes;
36
use java_bindings::{
47
exonum::{
@@ -14,16 +17,14 @@ use java_bindings::{
1417
};
1518
use lazy_static::lazy_static;
1619

17-
use std::sync::Arc;
18-
1920
lazy_static! {
2021
static ref VM: Arc<JavaVM> = create_vm_for_tests_with_classes();
2122
pub static ref EXECUTOR: Executor = Executor::new(VM.clone());
2223
}
2324

2425
const ARITHMETIC_EXCEPTION_CLASS: &str = "java/lang/ArithmeticException";
2526
const ILLEGAL_ARGUMENT_EXCEPTION_CLASS: &str = "java/lang/IllegalArgumentException";
26-
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/transaction/ExecutionException";
27+
const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/service/ExecutionException";
2728
const UNEXPECTED_EXECUTION_EXCEPTION_CLASS: &str =
2829
"com/exonum/binding/core/runtime/UnexpectedExecutionException";
2930
const STRING_CLASS: &str = "java/lang/String";

exonum-java-binding/core/rust/src/utils/jni_cache.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
//!
2121
//! See: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#jni_onload
2222
23+
use std::{os::raw::c_void, panic::catch_unwind};
24+
2325
use jni::{
2426
objects::{GlobalRef, JMethodID},
2527
sys::{jint, JNI_VERSION_1_8},
@@ -28,8 +30,6 @@ use jni::{
2830
use log::debug;
2931
use parking_lot::Once;
3032

31-
use std::{os::raw::c_void, panic::catch_unwind};
32-
3333
/// Invalid JNI version constant, signifying JNI_OnLoad failure.
3434
const INVALID_JNI_VERSION: jint = 0;
3535
const SERVICE_RUNTIME_ADAPTER_CLASS: &str = "com/exonum/binding/core/runtime/ServiceRuntimeAdapter";
@@ -97,7 +97,7 @@ unsafe fn cache_methods(env: &JNIEnv) {
9797
);
9898
EXECUTION_EXCEPTION_GET_ERROR_CODE = get_method_id(
9999
&env,
100-
"com/exonum/binding/core/transaction/ExecutionException",
100+
"com/exonum/binding/core/service/ExecutionException",
101101
"getErrorCode",
102102
"()B",
103103
);
@@ -159,10 +159,7 @@ unsafe fn cache_methods(env: &JNIEnv) {
159159
JAVA_LANG_ERROR = get_class(env, "java/lang/Error");
160160
JAVA_LANG_RUNTIME_EXCEPTION = get_class(env, "java/lang/RuntimeException");
161161
JAVA_LANG_ILLEGAL_ARGUMENT_EXCEPTION = get_class(env, "java/lang/IllegalArgumentException");
162-
EXECUTION_EXCEPTION = get_class(
163-
env,
164-
"com/exonum/binding/core/transaction/ExecutionException",
165-
);
162+
EXECUTION_EXCEPTION = get_class(env, "com/exonum/binding/core/service/ExecutionException");
166163
UNEXPECTED_EXECUTION_EXCEPTION = get_class(
167164
env,
168165
"com/exonum/binding/core/runtime/UnexpectedExecutionException",

exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.exonum.binding.core.blockchain.proofs.BlockProof;
2828
import com.exonum.binding.core.blockchain.proofs.IndexProof;
2929
import com.exonum.binding.core.service.Configuration;
30+
import com.exonum.binding.core.service.ExecutionContext;
3031
import com.exonum.binding.core.storage.database.Access;
3132
import com.exonum.binding.core.storage.indices.KeySetIndexProxy;
3233
import com.exonum.binding.core.storage.indices.ListIndex;
@@ -195,7 +196,7 @@ public BlockProof createBlockProof(long blockHeight) {
195196
* logic, an index may remain uninitialized indefinitely. Therefore, if proofs for an
196197
* empty index need to be created, it must be initialized early in the service lifecycle
197198
* (e.g., in {@link
198-
* com.exonum.binding.core.service.Service#initialize(BlockchainData, Configuration)}.
199+
* com.exonum.binding.core.service.Service#initialize(ExecutionContext, Configuration)}.
199200
* <!-- TODO: Simplify once initialization happens automatically: ECR-4121 -->
200201
*/
201202
public IndexProof createIndexProof(String fullIndexName) {

exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import com.exonum.binding.core.blockchain.BlockchainData;
2727
import com.exonum.binding.core.service.BlockCommittedEvent;
2828
import com.exonum.binding.core.service.BlockCommittedEventImpl;
29+
import com.exonum.binding.core.service.ExecutionContext;
30+
import com.exonum.binding.core.service.ExecutionException;
2931
import com.exonum.binding.core.storage.database.Snapshot;
30-
import com.exonum.binding.core.transaction.ExecutionException;
31-
import com.exonum.binding.core.transaction.TransactionContext;
3232
import com.exonum.binding.core.transport.Server;
3333
import com.exonum.messages.core.runtime.Errors.ErrorKind;
3434
import com.exonum.messages.core.runtime.Lifecycle.InstanceStatus;
@@ -200,7 +200,8 @@ public void initiateAddingService(BlockchainData blockchainData, ServiceInstance
200200
ServiceWrapper service = createServiceInstance(instanceSpec);
201201

202202
// Initialize it
203-
service.initialize(blockchainData, new ServiceConfiguration(configuration));
203+
ExecutionContext context = newContext(service, blockchainData).build();
204+
service.initialize(context, new ServiceConfiguration(configuration));
204205
}
205206

206207
// Log the initialization event
@@ -234,7 +235,8 @@ public void initiateResumingService(BlockchainData blockchainData,
234235
synchronized (lock) {
235236
checkStoppedService(instanceSpec.getId());
236237
ServiceWrapper service = createServiceInstance(instanceSpec);
237-
service.resume(blockchainData, arguments);
238+
ExecutionContext context = newContext(service, blockchainData).build();
239+
service.resume(context, arguments);
238240
}
239241
logger.info("Resumed service: {}", instanceSpec);
240242
} catch (Exception e) {
@@ -383,13 +385,9 @@ public void executeTransaction(int serviceId, String interfaceName, int txId,
383385
PublicKey authorPublicKey) {
384386
synchronized (lock) {
385387
ServiceWrapper service = getServiceById(serviceId);
386-
String serviceName = service.getName();
387-
TransactionContext context = TransactionContext.builder()
388-
.blockchainData(blockchainData)
388+
ExecutionContext context = newContext(service, blockchainData)
389389
.txMessageHash(txMessageHash)
390390
.authorPk(authorPublicKey)
391-
.serviceName(serviceName)
392-
.serviceId(serviceId)
393391
.build();
394392
try {
395393
service.executeTransaction(interfaceName, txId, arguments, callerServiceId, context);
@@ -410,7 +408,8 @@ public void beforeTransactions(int serviceId, BlockchainData blockchainData) {
410408
synchronized (lock) {
411409
ServiceWrapper service = getServiceById(serviceId);
412410
try {
413-
service.beforeTransactions(blockchainData);
411+
ExecutionContext context = newContext(service, blockchainData).build();
412+
service.beforeTransactions(context);
414413
} catch (Exception e) {
415414
logger.error("Service {} threw exception in beforeTransactions.", service.getName(), e);
416415
throw e;
@@ -435,7 +434,8 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
435434
synchronized (lock) {
436435
ServiceWrapper service = getServiceById(serviceId);
437436
try {
438-
service.afterTransactions(blockchainData);
437+
ExecutionContext context = newContext(service, blockchainData).build();
438+
service.afterTransactions(context);
439439
} catch (Exception e) {
440440
logger.error("Service {} threw exception in afterTransactions."
441441
+ " Any changes will be rolled-back", service.getName(), e);
@@ -444,6 +444,15 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
444444
}
445445
}
446446

447+
/** Creates a fully-initialized builder of a 'zero' context for the given service. */
448+
private static ExecutionContext.Builder newContext(ServiceWrapper service,
449+
BlockchainData blockchainData) {
450+
return ExecutionContext.builder()
451+
.blockchainData(blockchainData)
452+
.serviceName(service.getName())
453+
.serviceId(service.getId());
454+
}
455+
447456
/**
448457
* Notifies the services in the runtime of the block commit event.
449458
* @param snapshot a snapshot of the current database state

exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntimeAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import com.exonum.binding.core.blockchain.BlockchainData;
2424
import com.exonum.binding.core.proxy.Cleaner;
2525
import com.exonum.binding.core.proxy.CloseFailuresException;
26+
import com.exonum.binding.core.service.ExecutionException;
2627
import com.exonum.binding.core.storage.database.Snapshot;
27-
import com.exonum.binding.core.transaction.ExecutionException;
2828
import com.exonum.messages.core.runtime.Base.ArtifactId;
2929
import com.exonum.messages.core.runtime.Base.InstanceSpec;
3030
import com.exonum.messages.core.runtime.Lifecycle.InstanceStatus;

exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@
2020
import static com.google.common.base.Throwables.throwIfInstanceOf;
2121
import static java.lang.String.format;
2222

23-
import com.exonum.binding.core.blockchain.BlockchainData;
2423
import com.exonum.binding.core.service.BlockCommittedEvent;
2524
import com.exonum.binding.core.service.Configurable;
2625
import com.exonum.binding.core.service.Configuration;
26+
import com.exonum.binding.core.service.ExecutionContext;
27+
import com.exonum.binding.core.service.ExecutionException;
2728
import com.exonum.binding.core.service.Node;
2829
import com.exonum.binding.core.service.Service;
29-
import com.exonum.binding.core.transaction.ExecutionException;
30-
import com.exonum.binding.core.transaction.TransactionContext;
3130
import com.google.common.annotations.VisibleForTesting;
3231
import com.google.common.net.UrlEscapers;
3332
import com.google.inject.Inject;
@@ -102,16 +101,16 @@ int getId() {
102101
return instanceSpec.getId();
103102
}
104103

105-
void initialize(BlockchainData blockchainData, Configuration configuration) {
106-
callServiceMethod(() -> service.initialize(blockchainData, configuration));
104+
void initialize(ExecutionContext context, Configuration configuration) {
105+
callServiceMethod(() -> service.initialize(context, configuration));
107106
}
108107

109-
void resume(BlockchainData blockchainData, byte[] arguments) {
110-
callServiceMethod(() -> service.resume(blockchainData, arguments));
108+
void resume(ExecutionContext context, byte[] arguments) {
109+
callServiceMethod(() -> service.resume(context, arguments));
111110
}
112111

113112
void executeTransaction(String interfaceName, int txId, byte[] arguments, int callerServiceId,
114-
TransactionContext context) {
113+
ExecutionContext context) {
115114
switch (interfaceName) {
116115
case DEFAULT_INTERFACE_NAME: {
117116
executeIntrinsicTransaction(txId, arguments, context);
@@ -126,12 +125,12 @@ void executeTransaction(String interfaceName, int txId, byte[] arguments, int ca
126125
}
127126
}
128127

129-
private void executeIntrinsicTransaction(int txId, byte[] arguments, TransactionContext context) {
128+
private void executeIntrinsicTransaction(int txId, byte[] arguments, ExecutionContext context) {
130129
invoker.invokeTransaction(txId, arguments, context);
131130
}
132131

133132
private void executeConfigurableTransaction(int txId, byte[] arguments, int callerServiceId,
134-
TransactionContext context) {
133+
ExecutionContext context) {
135134
// Check the service implements Configurable
136135
checkArgument(service instanceof Configurable, "Service (%s) doesn't implement Configurable",
137136
getName());
@@ -141,27 +140,26 @@ private void executeConfigurableTransaction(int txId, byte[] arguments, int call
141140
callerServiceId, SUPERVISOR_SERVICE_ID);
142141
// Invoke the Configurable operation
143142
Configurable configurable = (Configurable) service;
144-
BlockchainData fork = context.getBlockchainData();
145143
Configuration config = new ServiceConfiguration(arguments);
146144
switch (txId) {
147145
case VERIFY_CONFIGURATION_TX_ID:
148-
callServiceMethod(() -> configurable.verifyConfiguration(fork, config));
146+
callServiceMethod(() -> configurable.verifyConfiguration(context, config));
149147
break;
150148
case APPLY_CONFIGURATION_TX_ID:
151-
callServiceMethod(() -> configurable.applyConfiguration(fork, config));
149+
callServiceMethod(() -> configurable.applyConfiguration(context, config));
152150
break;
153151
default:
154152
throw new IllegalArgumentException(
155153
format("Unknown txId (%d) in Configurable interface", txId));
156154
}
157155
}
158156

159-
void beforeTransactions(BlockchainData blockchainData) {
160-
callServiceMethod(() -> service.beforeTransactions(blockchainData));
157+
void beforeTransactions(ExecutionContext context) {
158+
callServiceMethod(() -> service.beforeTransactions(context));
161159
}
162160

163-
void afterTransactions(BlockchainData blockchainData) {
164-
callServiceMethod(() -> service.afterTransactions(blockchainData));
161+
void afterTransactions(ExecutionContext context) {
162+
callServiceMethod(() -> service.afterTransactions(context));
165163
}
166164

167165
/**

exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionExtractor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
import com.exonum.binding.common.serialization.Serializer;
2323
import com.exonum.binding.common.serialization.StandardSerializers;
24+
import com.exonum.binding.core.service.ExecutionContext;
2425
import com.exonum.binding.core.transaction.Transaction;
25-
import com.exonum.binding.core.transaction.TransactionContext;
2626
import com.google.common.annotations.VisibleForTesting;
2727
import com.google.protobuf.MessageLite;
2828
import java.lang.invoke.MethodHandle;
@@ -96,7 +96,7 @@ private static void validateTransactionMethod(Method transaction, Class<?> servi
9696
checkArgument(firstParameter == byte[].class || isProtobufArgument(firstParameter),
9797
String.format(errorMessage
9898
+ " But first parameter type was: %s", firstParameter.getName()));
99-
checkArgument(TransactionContext.class.isAssignableFrom(secondParameter),
99+
checkArgument(ExecutionContext.class.isAssignableFrom(secondParameter),
100100
String.format(errorMessage
101101
+ " But second parameter type was: %s", secondParameter.getName()));
102102
}

exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionInvoker.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

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

21+
import com.exonum.binding.core.service.ExecutionContext;
22+
import com.exonum.binding.core.service.ExecutionException;
2123
import com.exonum.binding.core.service.Service;
22-
import com.exonum.binding.core.transaction.ExecutionException;
23-
import com.exonum.binding.core.transaction.TransactionContext;
2424
import com.google.inject.Inject;
2525
import java.util.Map;
2626

@@ -52,7 +52,7 @@ final class TransactionInvoker {
5252
* @throws UnexpectedExecutionException if any other exception is thrown by
5353
* the transaction method, it is wrapped as cause
5454
*/
55-
void invokeTransaction(int transactionId, byte[] arguments, TransactionContext context) {
55+
void invokeTransaction(int transactionId, byte[] arguments, ExecutionContext context) {
5656
checkArgument(transactionMethods.containsKey(transactionId),
5757
"No method with transaction id (%s)", transactionId);
5858
TransactionMethod transactionMethod = transactionMethods.get(transactionId);

0 commit comments

Comments
 (0)