From 7d39fd15f72ae21d6f957e5e4d1cf8f1e87b8063 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Wed, 4 Mar 2020 18:19:48 +0200 Subject: [PATCH 1/9] Unify the context in transaction-like methods: [ECR-4303] 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). --- exonum-java-binding/CHANGELOG.md | 7 ++ .../binding/core/blockchain/Blockchain.java | 3 +- .../binding/core/runtime/ServiceRuntime.java | 35 ++++++-- .../binding/core/runtime/ServiceWrapper.java | 22 +++-- .../binding/core/service/Configurable.java | 20 ++--- .../binding/core/service/Configuration.java | 6 +- .../exonum/binding/core/service/Schema.java | 5 +- .../exonum/binding/core/service/Service.java | 25 +++--- .../core/transaction/TransactionContext.java | 21 +++-- .../ServiceRuntimeIntegrationTest.java | 39 ++++++-- .../ServiceWrapperIntegrationTest.java | 90 ++++++++----------- .../test/ServiceRuntimeIntegrationTest.java | 4 +- .../binding/qaservice/QaServiceImpl.java | 46 +++++----- .../binding/qaservice/QaServiceImplTest.java | 20 ++++- .../com/exonum/binding/testkit/TestKit.java | 4 +- .../exonum/binding/testkit/TestService.java | 5 +- 16 files changed, 201 insertions(+), 151 deletions(-) diff --git a/exonum-java-binding/CHANGELOG.md b/exonum-java-binding/CHANGELOG.md index 886405a23d..1971968da4 100644 --- a/exonum-java-binding/CHANGELOG.md +++ b/exonum-java-binding/CHANGELOG.md @@ -98,6 +98,13 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details. Any exceptions thrown in these methods are saved in the blockchain in `Blockchain#getCallErrors` and can be retrieved by any services or light clients. +- Replaced `BlockchainData` argument in transaction-like `Service` and + `Configurable` methods with `TransactionContext`. The `BlockchainData` + remains accessible via `TransactionContext#getBlockchainData`, + and service data via `TransactionContext#getServiceData`. + - `Blockchain#getTxResults` is replaced by `Blockchain#getCallErrors`. - Use `CallInBlocks` to concisely create `CallInBlock`s. - The specification of `Configurable` operations and `Service#initialize` diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java index f7e04997ff..ab2cde3b53 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java @@ -33,6 +33,7 @@ import com.exonum.binding.core.storage.indices.MapIndex; import com.exonum.binding.core.storage.indices.ProofListIndexProxy; import com.exonum.binding.core.storage.indices.ProofMapIndexProxy; +import com.exonum.binding.core.transaction.TransactionContext; import com.exonum.messages.core.Blockchain.CallInBlock; import com.exonum.messages.core.Blockchain.Config; import com.exonum.messages.core.Proofs; @@ -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(TransactionContext, Configuration)}. * */ public IndexProof createIndexProof(String fullIndexName) { diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java index d2d84d7859..939e818ae2 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java @@ -20,8 +20,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import com.exonum.binding.common.crypto.CryptoFunctions.Ed25519; import com.exonum.binding.common.crypto.PublicKey; import com.exonum.binding.common.hash.HashCode; +import com.exonum.binding.common.hash.Hashing; import com.exonum.binding.common.runtime.ServiceArtifactId; import com.exonum.binding.core.blockchain.BlockchainData; import com.exonum.binding.core.service.BlockCommittedEvent; @@ -66,6 +68,10 @@ public final class ServiceRuntime implements AutoCloseable { @VisibleForTesting static final String API_ROOT_PATH = "/api/services"; private static final Logger logger = LogManager.getLogger(ServiceRuntime.class); + @VisibleForTesting static final PublicKey ZERO_PK = + PublicKey.fromBytes(new byte[Ed25519.PUBLIC_KEY_BYTES]); + @VisibleForTesting static final HashCode ZERO_HASH = + HashCode.fromBytes(new byte[Hashing.DEFAULT_HASH_SIZE_BYTES]); private final ServiceLoader serviceLoader; private final ServicesFactory servicesFactory; @@ -200,7 +206,8 @@ public void initiateAddingService(BlockchainData blockchainData, ServiceInstance ServiceWrapper service = createServiceInstance(instanceSpec); // Initialize it - service.initialize(blockchainData, new ServiceConfiguration(configuration)); + TransactionContext context = newContext(service, blockchainData).build(); + service.initialize(context, new ServiceConfiguration(configuration)); } // Log the initialization event @@ -234,7 +241,8 @@ public void initiateResumingService(BlockchainData blockchainData, synchronized (lock) { checkStoppedService(instanceSpec.getId()); ServiceWrapper service = createServiceInstance(instanceSpec); - service.resume(blockchainData, arguments); + TransactionContext context = newContext(service, blockchainData).build(); + service.resume(context, arguments); } logger.info("Resumed service: {}", instanceSpec); } catch (Exception e) { @@ -383,13 +391,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) + TransactionContext context = newContext(service, blockchainData) .txMessageHash(txMessageHash) .authorPk(authorPublicKey) - .serviceName(serviceName) - .serviceId(serviceId) .build(); try { service.executeTransaction(interfaceName, txId, arguments, callerServiceId, context); @@ -410,7 +414,8 @@ public void beforeTransactions(int serviceId, BlockchainData blockchainData) { synchronized (lock) { ServiceWrapper service = getServiceById(serviceId); try { - service.beforeTransactions(blockchainData); + TransactionContext context = newContext(service, blockchainData).build(); + service.beforeTransactions(context); } catch (Exception e) { logger.error("Service {} threw exception in beforeTransactions.", service.getName(), e); throw e; @@ -435,7 +440,8 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) { synchronized (lock) { ServiceWrapper service = getServiceById(serviceId); try { - service.afterTransactions(blockchainData); + TransactionContext 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); @@ -444,6 +450,17 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) { } } + /** Creates a fully-initialized builder of a 'zero' context for the given service. */ + private static TransactionContext.Builder newContext(ServiceWrapper service, + BlockchainData blockchainData) { + return TransactionContext.builder() + .blockchainData(blockchainData) + .serviceName(service.getName()) + .serviceId(service.getId()) + .txMessageHash(ZERO_HASH) + .authorPk(ZERO_PK); + } + /** * Notifies the services in the runtime of the block commit event. * @param snapshot a snapshot of the current database state diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java index b16ecec98f..66aa757fa6 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java @@ -20,7 +20,6 @@ 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; @@ -102,12 +101,12 @@ int getId() { return instanceSpec.getId(); } - void initialize(BlockchainData blockchainData, Configuration configuration) { - callServiceMethod(() -> service.initialize(blockchainData, configuration)); + void initialize(TransactionContext context, Configuration configuration) { + callServiceMethod(() -> service.initialize(context, configuration)); } - void resume(BlockchainData blockchainData, byte[] arguments) { - callServiceMethod(() -> service.resume(blockchainData, arguments)); + void resume(TransactionContext context, byte[] arguments) { + callServiceMethod(() -> service.resume(context, arguments)); } void executeTransaction(String interfaceName, int txId, byte[] arguments, int callerServiceId, @@ -141,14 +140,13 @@ 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( @@ -156,12 +154,12 @@ private void executeConfigurableTransaction(int txId, byte[] arguments, int call } } - void beforeTransactions(BlockchainData blockchainData) { - callServiceMethod(() -> service.beforeTransactions(blockchainData)); + void beforeTransactions(TransactionContext context) { + callServiceMethod(() -> service.beforeTransactions(context)); } - void afterTransactions(BlockchainData blockchainData) { - callServiceMethod(() -> service.afterTransactions(blockchainData)); + void afterTransactions(TransactionContext context) { + callServiceMethod(() -> service.afterTransactions(context)); } /** diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java index f025ab03b9..d6a6e95bb2 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java @@ -16,7 +16,7 @@ package com.exonum.binding.core.service; -import com.exonum.binding.core.blockchain.BlockchainData; +import com.exonum.binding.core.transaction.TransactionContext; /** * A configurable Exonum service. Allows services to update their configuration through @@ -27,12 +27,11 @@ * and application of the new configuration. The protocol of the proposal and approval steps * is determined by the installed supervisor service. The verification and application * of the parameters are implemented by the service with - * {@link #verifyConfiguration(BlockchainData, Configuration)} - * and {@link #applyConfiguration(BlockchainData, Configuration)} methods. + * {@link #verifyConfiguration(TransactionContext, Configuration)} + * and {@link #applyConfiguration(TransactionContext, Configuration)} methods. * *

Services may use the same configuration parameters as - * in {@link Service#initialize(com.exonum.binding.core.blockchain.BlockchainData, Configuration)}, - * or different. + * in {@link Service#initialize(TransactionContext, Configuration)}, or different. * * - * @param blockchainData blockchain data accessor for this service. Not valid after this method - * returns + * @param context the execution context * @param arguments the service arguments * @throws ExecutionException if the arguments are not valid (e.g., * malformed, or do not meet the preconditions) */ - default void resume(BlockchainData blockchainData, byte[] arguments) { + default void resume(TransactionContext context, byte[] arguments) { // No actions by default } @@ -108,12 +106,12 @@ default void resume(BlockchainData blockchainData, byte[] arguments) { /** * An optional callback method invoked by the blockchain before any transactions - * in a block are executed. See {@link #afterTransactions(BlockchainData)} for details. + * in a block are executed. See {@link #afterTransactions(TransactionContext)} for details. * - * @see #afterTransactions(BlockchainData) + * @see #afterTransactions(TransactionContext) * @see com.exonum.binding.core.transaction.Transaction */ - default void beforeTransactions(BlockchainData blockchainData) {} + default void beforeTransactions(TransactionContext context) {} /** * Handles the changes made by all transactions included in the upcoming block. @@ -131,15 +129,14 @@ default void beforeTransactions(BlockchainData blockchainData) {} * in {@linkplain com.exonum.binding.core.blockchain.Blockchain#getCallErrors(long) * the registry of call errors} with appropriate error kinds. * - * @param blockchainData blockchain data accessor for this service. Not valid after this method - * returns + * @param context the execution context * @throws ExecutionException if an error occurs during the method execution; * it is saved as a call error of kind "service". Any other exceptions * are considered unexpected. They are saved with kind "unexpected". - * @see #beforeTransactions(BlockchainData) + * @see #beforeTransactions(TransactionContext) * @see com.exonum.binding.core.transaction.Transaction */ - default void afterTransactions(BlockchainData blockchainData) {} + default void afterTransactions(TransactionContext context) {} /** * Handles read-only block commit event. This handler is an optional callback method which is diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/TransactionContext.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/TransactionContext.java index 39c3419eaa..cf3c027eb5 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/TransactionContext.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/TransactionContext.java @@ -28,9 +28,14 @@ import com.exonum.binding.core.storage.database.Prefixed; /** - * Transaction context class. Contains required information for the transaction execution. - * The context is provided by the framework and users shouldn't create context instances manually - * except tests. + * A transaction execution context. The context provides access to the blockchain data + * for the executing service, and also contains the required information for the transaction + * execution. + * + *

The context is provided by the framework and users shouldn't create context instances manually + * except in tests. + * + * @see Transaction */ public interface TransactionContext { @@ -54,14 +59,16 @@ default Prefixed getServiceData() { /** * Returns SHA-256 hash of the {@linkplain TransactionMessage transaction message} that - * carried the payload of the transaction. - * Each transaction message is uniquely identified by its hash; the messages are persisted + * carried the payload of the transaction; or a zero hash if no message corresponds to this + * context. + * + *

Each transaction message is uniquely identified by its hash; the messages are persisted * in the {@linkplain Blockchain#getTxMessages() blockchain} and can be fetched by this hash. */ HashCode getTransactionMessageHash(); /** - * Returns public key of the transaction author. The corresponding transaction message + * Returns public key of the transaction author. The corresponding transaction message, if any, * is guaranteed to have a correct {@link CryptoFunctions#ed25519()} signature * with this public key. */ @@ -98,6 +105,8 @@ final class Builder { private String serviceName; private Integer serviceId; + // todo: Init hash and author to zeroes? Or always leave to the client (as now)? + /** * Sets the blockchain data for the context. */ diff --git a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java index 4660d7586a..62bcc9f0d4 100644 --- a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java +++ b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java @@ -16,6 +16,8 @@ package com.exonum.binding.core.runtime; +import static com.exonum.binding.core.runtime.ServiceRuntime.ZERO_HASH; +import static com.exonum.binding.core.runtime.ServiceRuntime.ZERO_PK; import static com.exonum.binding.core.runtime.ServiceWrapper.DEFAULT_INTERFACE_NAME; import static com.exonum.binding.test.Bytes.bytes; import static com.google.common.collect.Comparators.isInStrictOrder; @@ -183,6 +185,8 @@ void startAddingService() { .thenReturn(Optional.of(serviceDefinition)); ServiceWrapper serviceWrapper = mock(ServiceWrapper.class); + when(serviceWrapper.getName()).thenReturn(TEST_NAME); + when(serviceWrapper.getId()).thenReturn(TEST_ID); when(servicesFactory.createService(eq(serviceDefinition), eq(instanceSpec), any(ServiceNodeProxy.class))) .thenReturn(serviceWrapper); @@ -197,8 +201,9 @@ void startAddingService() { any(ServiceNodeProxy.class)); // and the service was configured + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); Configuration expectedConfig = new ServiceConfiguration(configuration); - verify(serviceWrapper).initialize(blockchainData, expectedConfig); + verify(serviceWrapper).initialize(expectedContext, expectedConfig); // but not committed assertThat(serviceRuntime.findService(TEST_NAME)).isEmpty(); @@ -238,15 +243,18 @@ void startAddingServiceBadInitialConfiguration() { .thenReturn(Optional.of(serviceDefinition)); ServiceWrapper serviceWrapper = mock(ServiceWrapper.class); + when(serviceWrapper.getName()).thenReturn(TEST_NAME); + when(serviceWrapper.getId()).thenReturn(TEST_ID); when(servicesFactory.createService(eq(serviceDefinition), eq(instanceSpec), any(ServiceNodeProxy.class))) .thenReturn(serviceWrapper); BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); byte[] configuration = anyConfiguration(); ServiceConfiguration expectedConfig = new ServiceConfiguration(configuration); doThrow(IllegalArgumentException.class).when(serviceWrapper) - .initialize(blockchainData, expectedConfig); + .initialize(expectedContext, expectedConfig); // Try to create and initialize the service assertThrows(IllegalArgumentException.class, @@ -409,6 +417,8 @@ void initializeResumingService() { .thenReturn(Optional.of(serviceDefinition)); ServiceWrapper serviceWrapper = mock(ServiceWrapper.class); + when(serviceWrapper.getName()).thenReturn(TEST_NAME); + when(serviceWrapper.getId()).thenReturn(TEST_ID); when(servicesFactory.createService(eq(serviceDefinition), eq(instanceSpec), any(ServiceNodeProxy.class))) .thenReturn(serviceWrapper); @@ -423,7 +433,8 @@ void initializeResumingService() { any(ServiceNodeProxy.class)); // and the service was resumed - verify(serviceWrapper).resume(blockchainData, arguments); + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); + verify(serviceWrapper).resume(expectedContext, arguments); // but not registered in the runtime yet: assertThat(serviceRuntime.findService(TEST_NAME)).isEmpty(); @@ -576,7 +587,8 @@ void beforeTransactionsSingleService() throws CloseFailuresException { serviceRuntime.beforeTransactions(TEST_ID, blockchainData); - verify(serviceWrapper).beforeTransactions(blockchainData); + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); + verify(serviceWrapper).beforeTransactions(expectedContext); } } @@ -589,7 +601,8 @@ void afterTransactionsSingleService() throws CloseFailuresException { serviceRuntime.afterTransactions(TEST_ID, blockchainData); - verify(serviceWrapper).afterTransactions(blockchainData); + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); + verify(serviceWrapper).afterTransactions(expectedContext); } } @@ -600,13 +613,14 @@ void afterTransactionsThrowingServiceExceptionPropagated() throws CloseFailuresE Fork fork = database.createFork(cleaner); BlockchainData blockchainData = BlockchainData.fromRawAccess(fork, TEST_NAME); RuntimeException serviceException = new RuntimeException("Service exception"); - doThrow(serviceException).when(serviceWrapper).afterTransactions(blockchainData); + TransactionContext expectedContext = zeroContext(TEST_ID, TEST_NAME, blockchainData); + doThrow(serviceException).when(serviceWrapper).afterTransactions(expectedContext); RuntimeException actual = assertThrows(serviceException.getClass(), () -> serviceRuntime.afterTransactions(TEST_ID, blockchainData)); assertThat(actual).isSameAs(serviceException); - verify(serviceWrapper).afterTransactions(blockchainData); + verify(serviceWrapper).afterTransactions(expectedContext); } } @@ -732,4 +746,15 @@ void afterCommitMultipleServicesWithFirstThrowing() { } } } + + private static TransactionContext zeroContext(int expectedId, String expectedName, + BlockchainData expectedData) { + return TransactionContext.builder() + .serviceName(expectedName) + .serviceId(expectedId) + .authorPk(ZERO_PK) + .txMessageHash(ZERO_HASH) + .blockchainData(expectedData) + .build(); + } } diff --git a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceWrapperIntegrationTest.java b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceWrapperIntegrationTest.java index 3565bc9372..13cd6567d6 100644 --- a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceWrapperIntegrationTest.java +++ b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceWrapperIntegrationTest.java @@ -24,7 +24,6 @@ import static com.exonum.binding.test.Bytes.bytes; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -83,65 +82,65 @@ void setUp() { @Test void initialize() { - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); Configuration config = new ServiceConfiguration(new byte[0]); - serviceWrapper.initialize(blockchainData, config); - verify(service).initialize(blockchainData, config); + serviceWrapper.initialize(context, config); + verify(service).initialize(context, config); } @Test void initializePropagatesExecutionException() { ExecutionException e = new ExecutionException((byte) 1); - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); Configuration config = new ServiceConfiguration(new byte[0]); - doThrow(e).when(service).initialize(blockchainData, config); + doThrow(e).when(service).initialize(context, config); ExecutionException actual = assertThrows(ExecutionException.class, - () -> serviceWrapper.initialize(blockchainData, config)); + () -> serviceWrapper.initialize(context, config)); assertThat(actual).isSameAs(e); } @Test void initializeWrapsRuntimeExceptions() { RuntimeException e = new RuntimeException("unexpected"); - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); Configuration config = new ServiceConfiguration(new byte[0]); - doThrow(e).when(service).initialize(blockchainData, config); + doThrow(e).when(service).initialize(context, config); Exception actual = assertThrows(UnexpectedExecutionException.class, - () -> serviceWrapper.initialize(blockchainData, config)); + () -> serviceWrapper.initialize(context, config)); assertThat(actual).hasCause(e); } @Test void resume() { - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); byte[] arguments = new byte[0]; - serviceWrapper.resume(blockchainData, arguments); - verify(service).resume(blockchainData, arguments); + serviceWrapper.resume(context, arguments); + verify(service).resume(context, arguments); } @Test void resumePropagatesExecutionException() { ExecutionException e = new ExecutionException((byte) 1); - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); byte[] arguments = new byte[0]; - doThrow(e).when(service).resume(blockchainData, arguments); + doThrow(e).when(service).resume(context, arguments); ExecutionException actual = assertThrows(ExecutionException.class, - () -> serviceWrapper.resume(blockchainData, arguments)); + () -> serviceWrapper.resume(context, arguments)); assertThat(actual).isSameAs(e); } @Test void resumeWrapsRuntimeExceptions() { RuntimeException e = new RuntimeException("unexpected"); - BlockchainData blockchainData = mock(BlockchainData.class); + TransactionContext context = anyContext().build(); byte[] arguments = new byte[0]; - doThrow(e).when(service).resume(blockchainData, arguments); + doThrow(e).when(service).resume(context, arguments); Exception actual = assertThrows(UnexpectedExecutionException.class, - () -> serviceWrapper.resume(blockchainData, arguments)); + () -> serviceWrapper.resume(context, arguments)); assertThat(actual).hasCause(e); } @@ -150,7 +149,7 @@ void executeTransactionDefaultInterface() { int txId = 2; byte[] arguments = bytes(1, 2, 3); - TransactionContext context = mock(TransactionContext.class); + TransactionContext context = anyContext().build(); serviceWrapper.executeTransaction(DEFAULT_INTERFACE_NAME, txId, arguments, 0, context); verify(txInvoker).invokeTransaction(txId, arguments, context); @@ -177,16 +176,13 @@ void executeVerifyConfiguration() { int txId = VERIFY_CONFIGURATION_TX_ID; byte[] arguments = bytes(1, 2, 3); - BlockchainData blockchainData = mock(BlockchainData.class); - TransactionContext context = anyContext() - .blockchainData(blockchainData) - .build(); + TransactionContext context = anyContext().build(); serviceWrapper.executeTransaction(interfaceName, txId, arguments, SUPERVISOR_SERVICE_ID, context); Configuration expected = new ServiceConfiguration(arguments); - verify(service).verifyConfiguration(blockchainData, expected); + verify(service).verifyConfiguration(context, expected); } @Test @@ -195,14 +191,11 @@ void executeVerifyConfigurationPropagatesExecutionException() { int txId = VERIFY_CONFIGURATION_TX_ID; byte[] arguments = bytes(1, 2, 3); - BlockchainData blockchainData = mock(BlockchainData.class); - TransactionContext context = anyContext() - .blockchainData(blockchainData) - .build(); + TransactionContext context = anyContext().build(); ExecutionException e = new ExecutionException((byte) 0); Configuration config = new ServiceConfiguration(arguments); - doThrow(e).when(service).verifyConfiguration(blockchainData, config); + doThrow(e).when(service).verifyConfiguration(context, config); ExecutionException actual = assertThrows(ExecutionException.class, () -> serviceWrapper.executeTransaction(interfaceName, txId, arguments, @@ -216,14 +209,11 @@ void executeVerifyConfigurationWrapsRuntimeExceptions() { int txId = VERIFY_CONFIGURATION_TX_ID; byte[] arguments = bytes(1, 2, 3); - BlockchainData blockchainData = mock(BlockchainData.class); - TransactionContext context = anyContext() - .blockchainData(blockchainData) - .build(); + TransactionContext context = anyContext().build(); RuntimeException e = new RuntimeException("unexpected"); Configuration config = new ServiceConfiguration(arguments); - doThrow(e).when(service).verifyConfiguration(blockchainData, config); + doThrow(e).when(service).verifyConfiguration(context, config); Exception actual = assertThrows(UnexpectedExecutionException.class, () -> serviceWrapper.executeTransaction(interfaceName, txId, arguments, @@ -237,16 +227,13 @@ void executeApplyConfiguration() { int txId = APPLY_CONFIGURATION_TX_ID; byte[] arguments = bytes(1, 2, 3); - BlockchainData blockchainData = mock(BlockchainData.class); - TransactionContext context = anyContext() - .blockchainData(blockchainData) - .build(); + TransactionContext context = anyContext().build(); serviceWrapper.executeTransaction(interfaceName, txId, arguments, SUPERVISOR_SERVICE_ID, context); Configuration expected = new ServiceConfiguration(arguments); - verify(service).applyConfiguration(blockchainData, expected); + verify(service).applyConfiguration(context, expected); } @ParameterizedTest @@ -256,10 +243,7 @@ void executeConfigurableOperationInvalidCallerId(int callerServiceId) { int txId = VERIFY_CONFIGURATION_TX_ID; byte[] arguments = bytes(1, 2, 3); - BlockchainData blockchainData = mock(BlockchainData.class); - TransactionContext context = anyContext() - .blockchainData(blockchainData) - .build(); + TransactionContext context = anyContext().build(); Exception e = assertThrows(IllegalArgumentException.class, () -> serviceWrapper.executeTransaction(interfaceName, txId, arguments, callerServiceId, @@ -323,30 +307,30 @@ void executeInvalidTransactionUnknownInterface(String interfaceName) { @Test void beforeTransactions() { - BlockchainData blockchainData = mock(BlockchainData.class); - serviceWrapper.beforeTransactions(blockchainData); - verify(service).beforeTransactions(blockchainData); + TransactionContext context = anyContext().build(); + serviceWrapper.beforeTransactions(context); + verify(service).beforeTransactions(context); } @Test void afterTransactionsPropagatesExecutionException() { ExecutionException e = new ExecutionException((byte) 0); - doThrow(e).when(service).afterTransactions(any(BlockchainData.class)); + TransactionContext context = anyContext().build(); + doThrow(e).when(service).afterTransactions(context); - BlockchainData blockchainData = mock(BlockchainData.class); ExecutionException actual = assertThrows(ExecutionException.class, - () -> serviceWrapper.afterTransactions(blockchainData)); + () -> serviceWrapper.afterTransactions(context)); assertThat(actual).isSameAs(e); } @Test void afterTransactionsKeepsRuntimeExceptionAsCause() { Exception e = new RuntimeException("Boom"); - doThrow(e).when(service).afterTransactions(any(BlockchainData.class)); + TransactionContext context = anyContext().build(); + doThrow(e).when(service).afterTransactions(context); - BlockchainData blockchainData = mock(BlockchainData.class); Exception actual = assertThrows(UnexpectedExecutionException.class, - () -> serviceWrapper.afterTransactions(blockchainData)); + () -> serviceWrapper.afterTransactions(context)); assertThat(actual).hasCause(e); } diff --git a/exonum-java-binding/integration-tests/src/test/java/com/exonum/binding/test/ServiceRuntimeIntegrationTest.java b/exonum-java-binding/integration-tests/src/test/java/com/exonum/binding/test/ServiceRuntimeIntegrationTest.java index 81e0d76ee8..ba1054eb46 100644 --- a/exonum-java-binding/integration-tests/src/test/java/com/exonum/binding/test/ServiceRuntimeIntegrationTest.java +++ b/exonum-java-binding/integration-tests/src/test/java/com/exonum/binding/test/ServiceRuntimeIntegrationTest.java @@ -22,13 +22,13 @@ import com.exonum.binding.common.blockchain.CallInBlocks; import com.exonum.binding.common.runtime.ServiceArtifactId; import com.exonum.binding.core.blockchain.Blockchain; -import com.exonum.binding.core.blockchain.BlockchainData; import com.exonum.binding.core.service.AbstractServiceModule; import com.exonum.binding.core.service.Node; import com.exonum.binding.core.service.Service; import com.exonum.binding.core.storage.database.Snapshot; import com.exonum.binding.core.storage.indices.ProofMapIndexProxy; import com.exonum.binding.core.transaction.ExecutionException; +import com.exonum.binding.core.transaction.TransactionContext; import com.exonum.binding.test.runtime.ServiceArtifactBuilder; import com.exonum.binding.testkit.TestKit; import com.exonum.messages.core.Blockchain.CallInBlock; @@ -90,7 +90,7 @@ public void createPublicApiHandlers(Node node, Router router) { } @Override - public void afterTransactions(BlockchainData blockchainData) { + public void afterTransactions(TransactionContext context) { throw new ExecutionException(AFTER_TX_ERROR_CODE); } } diff --git a/exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaServiceImpl.java b/exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaServiceImpl.java index 1e0d7fe118..dc39aef183 100644 --- a/exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaServiceImpl.java +++ b/exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaServiceImpl.java @@ -105,9 +105,9 @@ private QaSchema createDataSchema(BlockchainData blockchainData) { } @Override - public void initialize(BlockchainData blockchainData, Configuration configuration) { + public void initialize(TransactionContext context, Configuration configuration) { // Init the time oracle - updateTimeOracle(blockchainData, configuration); + updateTimeOracle(context, configuration); // Create the default counters: Stream.of( @@ -115,16 +115,16 @@ public void initialize(BlockchainData blockchainData, Configuration configuratio BEFORE_TXS_COUNTER_NAME, AFTER_TXS_COUNTER_NAME, AFTER_COMMIT_COUNTER_NAME) - .forEach(name -> createCounter(name, blockchainData)); + .forEach(name -> createCounter(name, context)); } @Override - public void resume(BlockchainData blockchainData, byte[] arguments) { + public void resume(TransactionContext context, byte[] arguments) { QaResumeArguments resumeArguments = parseResumeArguments(arguments); checkExecution(!resumeArguments.getShouldThrowException(), RESUME_SERVICE_ERROR.code); - createCounter(resumeArguments.getCounterName(), blockchainData); + createCounter(resumeArguments.getCounterName(), context); } @Override @@ -136,13 +136,13 @@ public void createPublicApiHandlers(Node node, Router router) { } @Override - public void beforeTransactions(BlockchainData blockchainData) { - incrementCounter(BEFORE_TXS_COUNTER_NAME, blockchainData); + public void beforeTransactions(TransactionContext context) { + incrementCounter(BEFORE_TXS_COUNTER_NAME, context); } @Override - public void afterTransactions(BlockchainData blockchainData) { - incrementCounter(AFTER_TXS_COUNTER_NAME, blockchainData); + public void afterTransactions(TransactionContext context) { + incrementCounter(AFTER_TXS_COUNTER_NAME, context); } /** @@ -273,28 +273,28 @@ private void checkBlockchainInitialized() { * @throws ExecutionException if time oracle name is empty */ @Override - public void verifyConfiguration(BlockchainData blockchainData, Configuration configuration) { + public void verifyConfiguration(TransactionContext context, Configuration configuration) { QaConfiguration config = configuration.getAsMessage(QaConfiguration.class); checkConfiguration(config); } @Override - public void applyConfiguration(BlockchainData blockchainData, Configuration configuration) { - updateTimeOracle(blockchainData, configuration); + public void applyConfiguration(TransactionContext context, Configuration configuration) { + updateTimeOracle(context, configuration); } @Override @Transaction(CREATE_COUNTER_TX_ID) public void createCounter(TxMessageProtos.CreateCounterTxBody arguments, TransactionContext context) { - String name = arguments.getName(); - checkArgument(!name.trim().isEmpty(), "Name must not be blank: '%s'", name); - - createCounter(name, context.getBlockchainData()); + String counterName = arguments.getName(); + createCounter(counterName, context); } - private void createCounter(String counterName, BlockchainData blockchainData) { - QaSchema schema = createDataSchema(blockchainData); + private void createCounter(String counterName, TransactionContext context) { + checkArgument(!counterName.trim().isEmpty(), "Name must not be blank: '%s'", counterName); + + QaSchema schema = createDataSchema(context.getBlockchainData()); MapIndex counters = schema.counters(); checkExecution(!counters.containsKey(counterName), @@ -308,11 +308,11 @@ private void createCounter(String counterName, BlockchainData blockchainData) { public void incrementCounter(TxMessageProtos.IncrementCounterTxBody arguments, TransactionContext context) { String counterName = arguments.getCounterName(); - incrementCounter(counterName, context.getBlockchainData()); + incrementCounter(counterName, context); } - private void incrementCounter(String counterName, BlockchainData blockchainData) { - QaSchema schema = createDataSchema(blockchainData); + private void incrementCounter(String counterName, TransactionContext context) { + QaSchema schema = createDataSchema(context.getBlockchainData()); ProofMapIndexProxy counters = schema.counters(); // Increment the counter if there is such. @@ -360,8 +360,8 @@ private void checkConfiguration(QaConfiguration config) { "Empty time oracle name: %s", timeOracleName); } - private void updateTimeOracle(BlockchainData blockchainData, Configuration configuration) { - QaSchema schema = createDataSchema(blockchainData); + private void updateTimeOracle(TransactionContext context, Configuration configuration) { + QaSchema schema = createDataSchema(context.getBlockchainData()); QaConfiguration config = configuration.getAsMessage(QaConfiguration.class); // Verify the configuration diff --git a/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java b/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java index 458db6156c..ed9aa35a24 100644 --- a/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java +++ b/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java @@ -41,8 +41,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import com.exonum.binding.common.crypto.CryptoFunctions.Ed25519; import com.exonum.binding.common.crypto.KeyPair; import com.exonum.binding.common.crypto.PublicKey; +import com.exonum.binding.common.hash.HashCode; +import com.exonum.binding.common.hash.Hashing; import com.exonum.binding.common.message.TransactionMessage; import com.exonum.binding.core.blockchain.Blockchain; import com.exonum.binding.core.blockchain.BlockchainData; @@ -53,6 +56,7 @@ import com.exonum.binding.core.storage.database.TemporaryDb; import com.exonum.binding.core.storage.indices.MapIndex; import com.exonum.binding.core.transaction.ExecutionException; +import com.exonum.binding.core.transaction.TransactionContext; import com.exonum.binding.qaservice.Config.QaConfiguration; import com.exonum.binding.qaservice.Config.QaResumeArguments; import com.exonum.binding.test.Integration; @@ -90,6 +94,9 @@ class QaServiceImplTest { private static final ZonedDateTime INITIAL_TIME = ZonedDateTime.now(ZoneOffset.UTC); + private static final PublicKey ZERO_PK = PublicKey.fromBytes(new byte[Ed25519.PUBLIC_KEY_BYTES]); + private static final HashCode ZERO_HASH = HashCode + .fromBytes(new byte[Hashing.DEFAULT_HASH_SIZE_BYTES]); private final FakeTimeProvider timeProvider = FakeTimeProvider.create(INITIAL_TIME); @@ -142,9 +149,16 @@ void resume() throws CloseFailuresException { Cleaner cleaner = new Cleaner()) { Fork fork = db.createFork(cleaner); BlockchainData blockchainData = BlockchainData.fromRawAccess(fork, QA_SERVICE_NAME); + TransactionContext context = TransactionContext.builder() + .serviceName(QA_SERVICE_NAME) + .serviceId(QA_SERVICE_ID) + .blockchainData(blockchainData) + .txMessageHash(ZERO_HASH) + .authorPk(ZERO_PK) + .build(); QaServiceImpl qaService = new QaServiceImpl(spec); - qaService.resume(blockchainData, arguments); + qaService.resume(context, arguments); QaSchema schema = new QaSchema(blockchainData); MapIndex counters = schema.counters(); @@ -160,12 +174,12 @@ void resumeShouldThrowException() { .setShouldThrowException(true) .build() .toByteArray(); - BlockchainData fork = mock(BlockchainData.class); + TransactionContext context = mock(TransactionContext.class); QaServiceImpl qaService = new QaServiceImpl(spec); ExecutionException exception = assertThrows(ExecutionException.class, - () -> qaService.resume(fork, arguments)); + () -> qaService.resume(context, arguments)); assertThat(exception.getErrorCode()).isEqualTo(RESUME_SERVICE_ERROR.code); } diff --git a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java index b8f666091b..cd0bdae3e9 100644 --- a/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java +++ b/exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java @@ -86,8 +86,8 @@ * from the pool are committed when a new block is created with {@link #createBlock()}. * *

When TestKit is created, Exonum blockchain instance is initialized — service instances are - * {@linkplain Service#initialize(com.exonum.binding.core.blockchain.BlockchainData, Configuration) - * initialized} and genesis block is committed. + * {@linkplain Service#initialize(com.exonum.binding.core.transaction.TransactionContext, + * Configuration) initialized} and genesis block is committed. * Then the {@linkplain Service#createPublicApiHandlers(Node, Router) public API handlers} are * created. * diff --git a/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestService.java b/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestService.java index 661b40d2fe..2076c941da 100644 --- a/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestService.java +++ b/exonum-java-binding/testkit/src/test/java/com/exonum/binding/testkit/TestService.java @@ -16,7 +16,6 @@ package com.exonum.binding.testkit; -import com.exonum.binding.core.blockchain.BlockchainData; import com.exonum.binding.core.runtime.ServiceInstanceSpec; import com.exonum.binding.core.service.AbstractService; import com.exonum.binding.core.service.BlockCommittedEvent; @@ -48,14 +47,14 @@ public TestService(ServiceInstanceSpec serviceSpec) { } @Override - public void initialize(BlockchainData blockchainData, Configuration configuration) { + public void initialize(TransactionContext context, Configuration configuration) { TestConfiguration initialConfiguration = configuration.getAsMessage(TestConfiguration.class); String configurationValue = initialConfiguration.getValue(); if (configurationValue.equals(THROWING_VALUE)) { throw new ExecutionException(ANY_ERROR_CODE, "Service configuration had an invalid value: " + configurationValue); } - TestSchema schema = new TestSchema(blockchainData.getExecutingServiceData()); + TestSchema schema = new TestSchema(context.getServiceData()); ProofMapIndexProxy testMap = schema.testMap(); testMap.put(INITIAL_ENTRY_KEY, configurationValue); } From f57ce18bd56c0a283bbb321a97a9547bc2e20874 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:02:12 +0200 Subject: [PATCH 2/9] Fix the tutorial code --- .../src/main/java/com/example/car/MyService.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java b/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java index 5007cf46d7..ef75069ace 100644 --- a/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java +++ b/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java @@ -17,14 +17,12 @@ package com.example.car; import com.example.car.messages.Transactions; -import com.example.car.messages.Transactions.AddVehicle; import com.example.car.messages.VehicleOuterClass.Vehicle; import com.exonum.binding.core.blockchain.BlockchainData; import com.exonum.binding.core.runtime.ServiceInstanceSpec; import com.exonum.binding.core.service.AbstractService; import com.exonum.binding.core.service.Configuration; import com.exonum.binding.core.service.Node; -import com.exonum.binding.core.storage.database.Prefixed; import com.exonum.binding.core.storage.indices.ProofMapIndexProxy; import com.exonum.binding.core.transaction.ExecutionException; import com.exonum.binding.core.transaction.Transaction; @@ -58,10 +56,6 @@ public void createPublicApiHandlers(Node node, Router router) { @Transaction(ADD_VEHICLE_TX_ID) public void addVehicle(Transactions.AddVehicle args, TransactionContext context) { var serviceData = context.getServiceData(); - addVehicle(args, serviceData); - } - - private void addVehicle(AddVehicle args, Prefixed serviceData) { var schema = new MySchema(serviceData); ProofMapIndexProxy vehicles = schema.vehicles(); @@ -108,15 +102,14 @@ public void changeOwner(Transactions.ChangeOwner args, TransactionContext contex // ci-block ci-initialize { @Override - public void initialize(BlockchainData blockchainData, Configuration configuration) { + public void initialize(TransactionContext context, Configuration configuration) { var testVehicles = List.of(vehicleArgs("V1", "Ford", "Focus", "Dave"), vehicleArgs("V2", "DMC", "DeLorean", "Emmett Brown"), vehicleArgs("V3", "Peugeot", "406", "Daniel Morales"), vehicleArgs("V4", "McLaren", "P1", "Weeknd")); - var serviceData = blockchainData.getExecutingServiceData(); for (var vehicle : testVehicles) { - addVehicle(vehicle, serviceData); + addVehicle(vehicle, context); } } From a7e6eeedf6d20304c574fae79eee7e40d5751886 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:03:44 +0200 Subject: [PATCH 3/9] Reorder code in MyService --- .../main/java/com/example/car/MyService.java | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java b/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java index ef75069ace..2c48994be5 100644 --- a/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java +++ b/exonum-java-binding/tutorials/car-registry/car-registry-service/src/main/java/com/example/car/MyService.java @@ -44,11 +44,30 @@ public MyService(ServiceInstanceSpec instanceSpec) { super(instanceSpec); } - // ci-block ci-createPublicApiHandlers { + // ci-block ci-initialize { @Override - public void createPublicApiHandlers(Node node, Router router) { - var apiController = new ApiController(this, node); - apiController.mount(router); + public void initialize(TransactionContext context, Configuration configuration) { + var testVehicles = + List.of(vehicleArgs("V1", "Ford", "Focus", "Dave"), + vehicleArgs("V2", "DMC", "DeLorean", "Emmett Brown"), + vehicleArgs("V3", "Peugeot", "406", "Daniel Morales"), + vehicleArgs("V4", "McLaren", "P1", "Weeknd")); + for (var vehicle : testVehicles) { + addVehicle(vehicle, context); + } + } + + private static Transactions.AddVehicle vehicleArgs(String id, String make, String model, + String owner) { + return Transactions.AddVehicle.newBuilder() + .setNewVehicle( + Vehicle.newBuilder() + .setId(id) + .setMake(make) + .setModel(model) + .setOwner(owner) + .build()) + .build(); } // } @@ -100,33 +119,6 @@ public void changeOwner(Transactions.ChangeOwner args, TransactionContext contex } // } - // ci-block ci-initialize { - @Override - public void initialize(TransactionContext context, Configuration configuration) { - var testVehicles = - List.of(vehicleArgs("V1", "Ford", "Focus", "Dave"), - vehicleArgs("V2", "DMC", "DeLorean", "Emmett Brown"), - vehicleArgs("V3", "Peugeot", "406", "Daniel Morales"), - vehicleArgs("V4", "McLaren", "P1", "Weeknd")); - for (var vehicle : testVehicles) { - addVehicle(vehicle, context); - } - } - - private static Transactions.AddVehicle vehicleArgs(String id, String make, String model, - String owner) { - return Transactions.AddVehicle.newBuilder() - .setNewVehicle( - Vehicle.newBuilder() - .setId(id) - .setMake(make) - .setModel(model) - .setOwner(owner) - .build()) - .build(); - } - // } - // ci-block ci-find-vehicle { /** * Returns a vehicle with the given id, if it exists; or {@code Optional.empty()} @@ -138,4 +130,12 @@ public Optional findVehicle(String id, BlockchainData blockchainData) { return Optional.ofNullable(vehicles.get(id)); } // } + + // ci-block ci-createPublicApiHandlers { + @Override + public void createPublicApiHandlers(Node node, Router router) { + var apiController = new ApiController(this, node); + apiController.mount(router); + } + // } } From 1722108bd6b5668217a2f5cfe5874fcd4714cfa8 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:08:19 +0200 Subject: [PATCH 4/9] Rename TransactionContext to ExecutionContext --- .../binding/core/blockchain/Blockchain.java | 4 +- .../binding/core/runtime/ServiceRuntime.java | 16 +++---- .../binding/core/runtime/ServiceWrapper.java | 16 +++---- .../core/runtime/TransactionExtractor.java | 4 +- .../core/runtime/TransactionInvoker.java | 4 +- .../core/runtime/TransactionMethod.java | 4 +- .../binding/core/service/Configurable.java | 14 +++--- .../binding/core/service/Configuration.java | 5 ++- .../exonum/binding/core/service/Schema.java | 3 +- .../exonum/binding/core/service/Service.java | 16 +++---- ...tionContext.java => ExecutionContext.java} | 6 +-- ...ext.java => InternalExecutionContext.java} | 6 +-- .../core/transaction/RawTransaction.java | 2 +- .../binding/core/transaction/Transaction.java | 2 +- .../ServiceRuntimeIntegrationTest.java | 20 ++++----- .../ServiceWrapperIntegrationTest.java | 44 +++++++++---------- .../runtime/TransactionExtractorTest.java | 24 +++++----- .../core/runtime/TransactionInvokerTest.java | 12 ++--- .../cryptocurrency/CryptocurrencyService.java | 6 +-- .../CryptocurrencyServiceImpl.java | 6 +-- .../binding/fakeservice/FakeService.java | 6 +-- .../test/ServiceRuntimeIntegrationTest.java | 4 +- .../exonum/binding/qaservice/QaService.java | 10 ++--- .../binding/qaservice/QaServiceImpl.java | 28 ++++++------ .../exonum/binding/qaservice/ErrorTxTest.java | 4 +- .../binding/qaservice/QaServiceImplTest.java | 6 +-- .../binding/qaservice/ThrowingTxTest.java | 4 +- .../binding/qaservice/TransactionUtils.java | 6 +-- .../com/exonum/binding/testkit/TestKit.java | 3 +- .../exonum/binding/testkit/TestService.java | 6 +-- .../main/java/com/example/car/MyService.java | 8 ++-- 31 files changed, 151 insertions(+), 148 deletions(-) rename exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/{TransactionContext.java => ExecutionContext.java} (96%) rename exonum-java-binding/core/src/main/java/com/exonum/binding/core/transaction/{InternalTransactionContext.java => InternalExecutionContext.java} (80%) diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java index ab2cde3b53..6198ad40fe 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java @@ -33,7 +33,7 @@ import com.exonum.binding.core.storage.indices.MapIndex; import com.exonum.binding.core.storage.indices.ProofListIndexProxy; import com.exonum.binding.core.storage.indices.ProofMapIndexProxy; -import com.exonum.binding.core.transaction.TransactionContext; +import com.exonum.binding.core.transaction.ExecutionContext; import com.exonum.messages.core.Blockchain.CallInBlock; import com.exonum.messages.core.Blockchain.Config; import com.exonum.messages.core.Proofs; @@ -196,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(TransactionContext, Configuration)}. + * com.exonum.binding.core.service.Service#initialize(ExecutionContext, Configuration)}. * */ public IndexProof createIndexProof(String fullIndexName) { diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java index 939e818ae2..62f4b8f510 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java @@ -29,8 +29,8 @@ import com.exonum.binding.core.service.BlockCommittedEvent; import com.exonum.binding.core.service.BlockCommittedEventImpl; import com.exonum.binding.core.storage.database.Snapshot; +import com.exonum.binding.core.transaction.ExecutionContext; 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; @@ -206,7 +206,7 @@ public void initiateAddingService(BlockchainData blockchainData, ServiceInstance ServiceWrapper service = createServiceInstance(instanceSpec); // Initialize it - TransactionContext context = newContext(service, blockchainData).build(); + ExecutionContext context = newContext(service, blockchainData).build(); service.initialize(context, new ServiceConfiguration(configuration)); } @@ -241,7 +241,7 @@ public void initiateResumingService(BlockchainData blockchainData, synchronized (lock) { checkStoppedService(instanceSpec.getId()); ServiceWrapper service = createServiceInstance(instanceSpec); - TransactionContext context = newContext(service, blockchainData).build(); + ExecutionContext context = newContext(service, blockchainData).build(); service.resume(context, arguments); } logger.info("Resumed service: {}", instanceSpec); @@ -391,7 +391,7 @@ public void executeTransaction(int serviceId, String interfaceName, int txId, PublicKey authorPublicKey) { synchronized (lock) { ServiceWrapper service = getServiceById(serviceId); - TransactionContext context = newContext(service, blockchainData) + ExecutionContext context = newContext(service, blockchainData) .txMessageHash(txMessageHash) .authorPk(authorPublicKey) .build(); @@ -414,7 +414,7 @@ public void beforeTransactions(int serviceId, BlockchainData blockchainData) { synchronized (lock) { ServiceWrapper service = getServiceById(serviceId); try { - TransactionContext context = newContext(service, blockchainData).build(); + ExecutionContext context = newContext(service, blockchainData).build(); service.beforeTransactions(context); } catch (Exception e) { logger.error("Service {} threw exception in beforeTransactions.", service.getName(), e); @@ -440,7 +440,7 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) { synchronized (lock) { ServiceWrapper service = getServiceById(serviceId); try { - TransactionContext context = newContext(service, blockchainData).build(); + ExecutionContext context = newContext(service, blockchainData).build(); service.afterTransactions(context); } catch (Exception e) { logger.error("Service {} threw exception in afterTransactions." @@ -451,9 +451,9 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) { } /** Creates a fully-initialized builder of a 'zero' context for the given service. */ - private static TransactionContext.Builder newContext(ServiceWrapper service, + private static ExecutionContext.Builder newContext(ServiceWrapper service, BlockchainData blockchainData) { - return TransactionContext.builder() + return ExecutionContext.builder() .blockchainData(blockchainData) .serviceName(service.getName()) .serviceId(service.getId()) diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java index 66aa757fa6..a09d9a60ff 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceWrapper.java @@ -26,7 +26,7 @@ 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.exonum.binding.core.transaction.ExecutionContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.net.UrlEscapers; import com.google.inject.Inject; @@ -101,16 +101,16 @@ int getId() { return instanceSpec.getId(); } - void initialize(TransactionContext context, Configuration configuration) { + void initialize(ExecutionContext context, Configuration configuration) { callServiceMethod(() -> service.initialize(context, configuration)); } - void resume(TransactionContext context, byte[] 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); @@ -125,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()); @@ -154,11 +154,11 @@ private void executeConfigurableTransaction(int txId, byte[] arguments, int call } } - void beforeTransactions(TransactionContext context) { + void beforeTransactions(ExecutionContext context) { callServiceMethod(() -> service.beforeTransactions(context)); } - void afterTransactions(TransactionContext context) { + void afterTransactions(ExecutionContext context) { callServiceMethod(() -> service.afterTransactions(context)); } diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionExtractor.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionExtractor.java index e33c3bff7b..748cfa8f31 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionExtractor.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionExtractor.java @@ -21,8 +21,8 @@ import com.exonum.binding.common.serialization.Serializer; import com.exonum.binding.common.serialization.StandardSerializers; +import com.exonum.binding.core.transaction.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; @@ -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())); } diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionInvoker.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionInvoker.java index 6766c2f4d2..8296226ea6 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionInvoker.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionInvoker.java @@ -19,8 +19,8 @@ import static com.google.common.base.Preconditions.checkArgument; import com.exonum.binding.core.service.Service; +import com.exonum.binding.core.transaction.ExecutionContext; import com.exonum.binding.core.transaction.ExecutionException; -import com.exonum.binding.core.transaction.TransactionContext; import com.google.inject.Inject; import java.util.Map; @@ -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); diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionMethod.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionMethod.java index 3b821ed64b..f6a7eca510 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionMethod.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/TransactionMethod.java @@ -19,7 +19,7 @@ import com.exonum.binding.common.serialization.Serializer; import com.exonum.binding.core.service.Service; import com.exonum.binding.core.transaction.ExecutionException; -import com.exonum.binding.core.transaction.TransactionContext; +import com.exonum.binding.core.transaction.ExecutionContext; import java.lang.invoke.MethodHandle; import java.lang.invoke.WrongMethodTypeException; @@ -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); diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java index d6a6e95bb2..1a0c7774dc 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java @@ -16,7 +16,7 @@ package com.exonum.binding.core.service; -import com.exonum.binding.core.transaction.TransactionContext; +import com.exonum.binding.core.transaction.ExecutionContext; /** * A configurable Exonum service. Allows services to update their configuration through @@ -27,11 +27,11 @@ * and application of the new configuration. The protocol of the proposal and approval steps * is determined by the installed supervisor service. The verification and application * of the parameters are implemented by the service with - * {@link #verifyConfiguration(TransactionContext, Configuration)} - * and {@link #applyConfiguration(TransactionContext, Configuration)} methods. + * {@link #verifyConfiguration(ExecutionContext, Configuration)} + * and {@link #applyConfiguration(ExecutionContext, Configuration)} methods. * *

Services may use the same configuration parameters as - * in {@link Service#initialize(TransactionContext, Configuration)}, or different. + * in {@link Service#initialize(ExecutionContext, Configuration)}, or different. * - `Blockchain#getTxResults` is replaced by `Blockchain#getCallErrors`. - Use `CallInBlocks` to concisely create `CallInBlock`s. - The specification of `Configurable` operations and `Service#initialize` From c97d1f5ee46e40fb540f6f3fdbe3aef5c377e9d8 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:24:41 +0200 Subject: [PATCH 7/9] Fix native --- .../integration_tests/benches/jni_cache.rs | 16 +++++++------- .../tests/runtime_error_handling.rs | 21 ++++++++++--------- .../core/rust/src/utils/jni_cache.rs | 14 ++++++------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs b/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs index 27074c789c..91bd5d9656 100644 --- a/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs +++ b/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs @@ -18,20 +18,20 @@ extern crate java_bindings; extern crate lazy_static; extern crate test; +use std::sync::Arc; +use test::{Bencher, black_box}; + use integration_tests::vm::create_vm_for_benchmarks_with_classes; use java_bindings::{ + Executor, jni::{ - objects::{JObject, JValue}, - JNIEnv, JavaVM, + JavaVM, + JNIEnv, objects::{JObject, JValue}, }, - utils::{convert_to_string, get_class_name, jni_cache}, - Executor, JniResult, + JniResult, utils::{convert_to_string, get_class_name, jni_cache}, }; -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 = create_vm_for_benchmarks_with_classes(); diff --git a/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs b/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs index 9493116ac7..a3a234450e 100644 --- a/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs +++ b/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs @@ -1,21 +1,22 @@ +use std::sync::Arc; + use exonum_derive::ExecutionFail; + use integration_tests::vm::create_vm_for_tests_with_classes; use java_bindings::{ + Error, + Executor, exonum::{ self, - runtime::ExecutionError, runtime::{ErrorKind, ErrorMatch}, - }, - jni::{ - objects::{JObject, JThrowable}, - JNIEnv, JavaVM, - }, - jni_call_default, jni_call_transaction, Error, Executor, JniResult, + runtime::ExecutionError, + }, jni::{ + JavaVM, + JNIEnv, objects::{JObject, JThrowable}, + }, jni_call_default, jni_call_transaction, JniResult, }; use lazy_static::lazy_static; -use std::sync::Arc; - lazy_static! { static ref VM: Arc = create_vm_for_tests_with_classes(); pub static ref EXECUTOR: Executor = Executor::new(VM.clone()); @@ -23,7 +24,7 @@ lazy_static! { 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"; diff --git a/exonum-java-binding/core/rust/src/utils/jni_cache.rs b/exonum-java-binding/core/rust/src/utils/jni_cache.rs index a2a0e75dca..7aa6d7e777 100644 --- a/exonum-java-binding/core/rust/src/utils/jni_cache.rs +++ b/exonum-java-binding/core/rust/src/utils/jni_cache.rs @@ -20,16 +20,16 @@ //! //! 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}, - JNIEnv, JavaVM, + JavaVM, + JNIEnv, + objects::{GlobalRef, JMethodID}, sys::{jint, JNI_VERSION_1_8}, }; 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"; @@ -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", ); @@ -161,7 +161,7 @@ unsafe fn cache_methods(env: &JNIEnv) { JAVA_LANG_ILLEGAL_ARGUMENT_EXCEPTION = get_class(env, "java/lang/IllegalArgumentException"); EXECUTION_EXCEPTION = get_class( env, - "com/exonum/binding/core/transaction/ExecutionException", + "com/exonum/binding/core/service/ExecutionException", ); UNEXPECTED_EXECUTION_EXCEPTION = get_class( env, From d0aee06453155dd5f2fffb7b73d44eb83dc47c84 Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:45:17 +0200 Subject: [PATCH 8/9] Return Optional for optional context properties. --- exonum-java-binding/CHANGELOG.md | 3 +++ .../binding/core/runtime/ServiceRuntime.java | 10 +------ .../core/service/ExecutionContext.java | 26 ++++++++++--------- .../service/InternalExecutionContext.java | 14 ++++++---- .../ServiceRuntimeIntegrationTest.java | 4 --- .../CryptocurrencyServiceImpl.java | 6 ++--- .../binding/qaservice/QaServiceImplTest.java | 8 ------ 7 files changed, 30 insertions(+), 41 deletions(-) diff --git a/exonum-java-binding/CHANGELOG.md b/exonum-java-binding/CHANGELOG.md index d2882da0eb..5fb089d990 100644 --- a/exonum-java-binding/CHANGELOG.md +++ b/exonum-java-binding/CHANGELOG.md @@ -101,6 +101,9 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details. `Configurable` methods with `ExecutionContext`. The `BlockchainData` remains accessible via `TransactionContext#getBlockchainData`, and service data via `TransactionContext#getServiceData`. + - Made `getTransactionMessageHash` return `Optional` 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`). diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java index 7baa62cd0e..ed0ad47077 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/runtime/ServiceRuntime.java @@ -20,10 +20,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import com.exonum.binding.common.crypto.CryptoFunctions.Ed25519; import com.exonum.binding.common.crypto.PublicKey; import com.exonum.binding.common.hash.HashCode; -import com.exonum.binding.common.hash.Hashing; import com.exonum.binding.common.runtime.ServiceArtifactId; import com.exonum.binding.core.blockchain.BlockchainData; import com.exonum.binding.core.service.BlockCommittedEvent; @@ -68,10 +66,6 @@ public final class ServiceRuntime implements AutoCloseable { @VisibleForTesting static final String API_ROOT_PATH = "/api/services"; private static final Logger logger = LogManager.getLogger(ServiceRuntime.class); - @VisibleForTesting static final PublicKey ZERO_PK = - PublicKey.fromBytes(new byte[Ed25519.PUBLIC_KEY_BYTES]); - @VisibleForTesting static final HashCode ZERO_HASH = - HashCode.fromBytes(new byte[Hashing.DEFAULT_HASH_SIZE_BYTES]); private final ServiceLoader serviceLoader; private final ServicesFactory servicesFactory; @@ -456,9 +450,7 @@ private static ExecutionContext.Builder newContext(ServiceWrapper service, return ExecutionContext.builder() .blockchainData(blockchainData) .serviceName(service.getName()) - .serviceId(service.getId()) - .txMessageHash(ZERO_HASH) - .authorPk(ZERO_PK); + .serviceId(service.getId()); } /** diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/ExecutionContext.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/ExecutionContext.java index 3f7c427d53..ada88f1cbd 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/ExecutionContext.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/ExecutionContext.java @@ -27,9 +27,11 @@ import com.exonum.binding.core.runtime.ServiceInstanceSpec; import com.exonum.binding.core.storage.database.Prefixed; import com.exonum.binding.core.transaction.Transaction; +import java.util.Optional; +import javax.annotation.Nullable; /** - * A transaction execution context. The context provides access to the blockchain data + * An execution context. The context provides access to the blockchain data * for the executing service, and also contains the required information for the transaction * execution. * @@ -60,20 +62,22 @@ default Prefixed getServiceData() { /** * Returns SHA-256 hash of the {@linkplain TransactionMessage transaction message} that - * carried the payload of the transaction; or a zero hash if no message corresponds to this - * context. + * carried the payload of the transaction; or {@code Optional.empty()} if no message corresponds + * to this context. * *

Each transaction message is uniquely identified by its hash; the messages are persisted * in the {@linkplain Blockchain#getTxMessages() blockchain} and can be fetched by this hash. */ - HashCode getTransactionMessageHash(); + Optional getTransactionMessageHash(); /** - * Returns public key of the transaction author. The corresponding transaction message, if any, - * is guaranteed to have a correct {@link CryptoFunctions#ed25519()} signature - * with this public key. + * Returns public key of the transaction author; or {@code Optional.empty()} if no transaction + * message corresponds to this context. + * + *

The corresponding transaction message, if any, is guaranteed to have a correct + * {@link CryptoFunctions#ed25519()} signature with this public key. */ - PublicKey getAuthorPk(); + Optional getAuthorPk(); /** * Returns the name of the service instance. @@ -106,8 +110,6 @@ final class Builder { private String serviceName; private Integer serviceId; - // todo: Init hash and author to zeroes? Or always leave to the client (as now)? - /** * Sets the blockchain data for the context. */ @@ -119,7 +121,7 @@ public Builder blockchainData(BlockchainData blockchainData) { /** * Sets transaction message hash for the context. */ - public Builder txMessageHash(HashCode hash) { + public Builder txMessageHash(@Nullable HashCode hash) { this.hash = hash; return this; } @@ -127,7 +129,7 @@ public Builder txMessageHash(HashCode hash) { /** * Sets transaction author public key for the context. */ - public Builder authorPk(PublicKey authorPk) { + public Builder authorPk(@Nullable PublicKey authorPk) { this.authorPk = authorPk; return this; } diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/InternalExecutionContext.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/InternalExecutionContext.java index 189584b179..8bc3254da2 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/InternalExecutionContext.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/InternalExecutionContext.java @@ -20,6 +20,8 @@ import com.exonum.binding.common.hash.HashCode; import com.exonum.binding.core.blockchain.BlockchainData; import com.google.auto.value.AutoValue; +import java.util.Optional; +import javax.annotation.Nullable; /** * Default implementation of the transaction context. @@ -27,10 +29,12 @@ @AutoValue abstract class InternalExecutionContext implements ExecutionContext { - public static InternalExecutionContext newInstance(BlockchainData blockchainData, HashCode hash, - PublicKey authorPk, String serviceName, - int serviceId) { - return new AutoValue_InternalExecutionContext(blockchainData, hash, authorPk, serviceName, - serviceId); + public static InternalExecutionContext newInstance(BlockchainData blockchainData, + @Nullable HashCode txMessageHash, @Nullable PublicKey authorPk, String serviceName, + int serviceId) { + var txMessageHashOpt = Optional.ofNullable(txMessageHash); + var authorPkOpt = Optional.ofNullable(authorPk); + return new AutoValue_InternalExecutionContext(blockchainData, txMessageHashOpt, authorPkOpt, + serviceName, serviceId); } } diff --git a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java index 7165b6cb98..31162fedfe 100644 --- a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java +++ b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/runtime/ServiceRuntimeIntegrationTest.java @@ -16,8 +16,6 @@ package com.exonum.binding.core.runtime; -import static com.exonum.binding.core.runtime.ServiceRuntime.ZERO_HASH; -import static com.exonum.binding.core.runtime.ServiceRuntime.ZERO_PK; import static com.exonum.binding.core.runtime.ServiceWrapper.DEFAULT_INTERFACE_NAME; import static com.exonum.binding.test.Bytes.bytes; import static com.google.common.collect.Comparators.isInStrictOrder; @@ -752,8 +750,6 @@ private static ExecutionContext zeroContext(int expectedId, String expectedName, return ExecutionContext.builder() .serviceName(expectedName) .serviceId(expectedId) - .authorPk(ZERO_PK) - .txMessageHash(ZERO_HASH) .blockchainData(expectedData) .build(); } diff --git a/exonum-java-binding/cryptocurrency-demo/src/main/java/com/exonum/binding/cryptocurrency/CryptocurrencyServiceImpl.java b/exonum-java-binding/cryptocurrency-demo/src/main/java/com/exonum/binding/cryptocurrency/CryptocurrencyServiceImpl.java index 43277eac82..20a808e33b 100644 --- a/exonum-java-binding/cryptocurrency-demo/src/main/java/com/exonum/binding/cryptocurrency/CryptocurrencyServiceImpl.java +++ b/exonum-java-binding/cryptocurrency-demo/src/main/java/com/exonum/binding/cryptocurrency/CryptocurrencyServiceImpl.java @@ -105,7 +105,7 @@ public List getWalletHistory(PublicKey ownerKey) { @Override @Transaction(CREATE_WALLET_TX_ID) public void createWallet(TxMessageProtos.CreateWalletTx arguments, ExecutionContext context) { - PublicKey ownerPublicKey = context.getAuthorPk(); + PublicKey ownerPublicKey = context.getAuthorPk().orElseThrow(); CryptocurrencySchema schema = createDataSchema(context.getBlockchainData()); MapIndex wallets = schema.wallets(); @@ -127,7 +127,7 @@ public void transfer(TxMessageProtos.TransferTx arguments, ExecutionContext cont checkExecution(0 < sum, NON_POSITIVE_TRANSFER_AMOUNT.errorCode, "Non-positive transfer amount: " + sum); - PublicKey fromWallet = context.getAuthorPk(); + PublicKey fromWallet = context.getAuthorPk().orElseThrow(); PublicKey toWallet = toPublicKey(arguments.getToWallet()); checkExecution(!fromWallet.equals(toWallet), SAME_SENDER_AND_RECEIVER.errorCode); @@ -145,7 +145,7 @@ public void transfer(TxMessageProtos.TransferTx arguments, ExecutionContext cont wallets.put(toWallet, new Wallet(to.getBalance() + sum)); // Update the transaction history of each wallet - HashCode messageHash = context.getTransactionMessageHash(); + HashCode messageHash = context.getTransactionMessageHash().orElseThrow(); schema.transactionsHistory(fromWallet).add(messageHash); schema.transactionsHistory(toWallet).add(messageHash); } diff --git a/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java b/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java index 9ed9b87db8..220b2580bb 100644 --- a/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java +++ b/exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java @@ -41,11 +41,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import com.exonum.binding.common.crypto.CryptoFunctions.Ed25519; import com.exonum.binding.common.crypto.KeyPair; import com.exonum.binding.common.crypto.PublicKey; -import com.exonum.binding.common.hash.HashCode; -import com.exonum.binding.common.hash.Hashing; import com.exonum.binding.common.message.TransactionMessage; import com.exonum.binding.core.blockchain.Blockchain; import com.exonum.binding.core.blockchain.BlockchainData; @@ -94,9 +91,6 @@ class QaServiceImplTest { private static final ZonedDateTime INITIAL_TIME = ZonedDateTime.now(ZoneOffset.UTC); - private static final PublicKey ZERO_PK = PublicKey.fromBytes(new byte[Ed25519.PUBLIC_KEY_BYTES]); - private static final HashCode ZERO_HASH = HashCode - .fromBytes(new byte[Hashing.DEFAULT_HASH_SIZE_BYTES]); private final FakeTimeProvider timeProvider = FakeTimeProvider.create(INITIAL_TIME); @@ -153,8 +147,6 @@ void resume() throws CloseFailuresException { .serviceName(QA_SERVICE_NAME) .serviceId(QA_SERVICE_ID) .blockchainData(blockchainData) - .txMessageHash(ZERO_HASH) - .authorPk(ZERO_PK) .build(); QaServiceImpl qaService = new QaServiceImpl(spec); From 0e2595d5386ccc89994c7ba59861bc02c6d9032c Mon Sep 17 00:00:00 2001 From: Dmitry Timofeev Date: Thu, 12 Mar 2020 17:52:19 +0200 Subject: [PATCH 9/9] Cargo fmt --- .../rust/integration_tests/benches/jni_cache.rs | 10 +++++----- .../tests/runtime_error_handling.rs | 14 +++++++------- .../core/rust/src/utils/jni_cache.rs | 11 ++++------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs b/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs index 91bd5d9656..164939d2d1 100644 --- a/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs +++ b/exonum-java-binding/core/rust/integration_tests/benches/jni_cache.rs @@ -19,16 +19,16 @@ extern crate lazy_static; extern crate test; use std::sync::Arc; -use test::{Bencher, black_box}; +use test::{black_box, Bencher}; use integration_tests::vm::create_vm_for_benchmarks_with_classes; use java_bindings::{ - Executor, jni::{ - JavaVM, - JNIEnv, objects::{JObject, JValue}, + objects::{JObject, JValue}, + JNIEnv, JavaVM, }, - JniResult, utils::{convert_to_string, get_class_name, jni_cache}, + utils::{convert_to_string, get_class_name, jni_cache}, + Executor, JniResult, }; const EXECUTION_EXCEPTION_CLASS: &str = "com/exonum/binding/core/service/ExecutionException"; diff --git a/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs b/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs index a3a234450e..49870af864 100644 --- a/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs +++ b/exonum-java-binding/core/rust/integration_tests/tests/runtime_error_handling.rs @@ -4,16 +4,16 @@ use exonum_derive::ExecutionFail; use integration_tests::vm::create_vm_for_tests_with_classes; use java_bindings::{ - Error, - Executor, exonum::{ self, - runtime::{ErrorKind, ErrorMatch}, runtime::ExecutionError, - }, jni::{ - JavaVM, - JNIEnv, objects::{JObject, JThrowable}, - }, jni_call_default, jni_call_transaction, JniResult, + runtime::{ErrorKind, ErrorMatch}, + }, + jni::{ + objects::{JObject, JThrowable}, + JNIEnv, JavaVM, + }, + jni_call_default, jni_call_transaction, Error, Executor, JniResult, }; use lazy_static::lazy_static; diff --git a/exonum-java-binding/core/rust/src/utils/jni_cache.rs b/exonum-java-binding/core/rust/src/utils/jni_cache.rs index 7aa6d7e777..eb3be774fe 100644 --- a/exonum-java-binding/core/rust/src/utils/jni_cache.rs +++ b/exonum-java-binding/core/rust/src/utils/jni_cache.rs @@ -23,9 +23,9 @@ use std::{os::raw::c_void, panic::catch_unwind}; use jni::{ - JavaVM, - JNIEnv, - objects::{GlobalRef, JMethodID}, sys::{jint, JNI_VERSION_1_8}, + objects::{GlobalRef, JMethodID}, + sys::{jint, JNI_VERSION_1_8}, + JNIEnv, JavaVM, }; use log::debug; use parking_lot::Once; @@ -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/service/ExecutionException", - ); + EXECUTION_EXCEPTION = get_class(env, "com/exonum/binding/core/service/ExecutionException"); UNEXPECTED_EXECUTION_EXCEPTION = get_class( env, "com/exonum/binding/core/runtime/UnexpectedExecutionException",