Skip to content

Commit 7d39fd1

Browse files
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).
1 parent 17fddca commit 7d39fd1

File tree

16 files changed

+201
-151
lines changed

16 files changed

+201
-151
lines changed

exonum-java-binding/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ stable Exonum release. See [release notes][exonum-1.0.0-rc.1] for details.
9898
Any exceptions thrown in these methods are saved in the blockchain
9999
in `Blockchain#getCallErrors` and can be retrieved by any services or
100100
light clients.
101+
- Replaced `BlockchainData` argument in transaction-like `Service` and
102+
`Configurable` methods with `TransactionContext`. The `BlockchainData`
103+
remains accessible via `TransactionContext#getBlockchainData`,
104+
and service data via `TransactionContext#getServiceData`.
105+
<!-- TODO: Shall we rename it into `ExecutionContext` (as
106+
TransactionExecutionException has become ExecutionException)?
107+
-->
101108
- `Blockchain#getTxResults` is replaced by `Blockchain#getCallErrors`.
102109
- Use `CallInBlocks` to concisely create `CallInBlock`s.
103110
- The specification of `Configurable` operations and `Service#initialize`

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
@@ -33,6 +33,7 @@
3333
import com.exonum.binding.core.storage.indices.MapIndex;
3434
import com.exonum.binding.core.storage.indices.ProofListIndexProxy;
3535
import com.exonum.binding.core.storage.indices.ProofMapIndexProxy;
36+
import com.exonum.binding.core.transaction.TransactionContext;
3637
import com.exonum.messages.core.Blockchain.CallInBlock;
3738
import com.exonum.messages.core.Blockchain.Config;
3839
import com.exonum.messages.core.Proofs;
@@ -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(TransactionContext, 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: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import static com.google.common.base.Preconditions.checkNotNull;
2121
import static com.google.common.base.Preconditions.checkState;
2222

23+
import com.exonum.binding.common.crypto.CryptoFunctions.Ed25519;
2324
import com.exonum.binding.common.crypto.PublicKey;
2425
import com.exonum.binding.common.hash.HashCode;
26+
import com.exonum.binding.common.hash.Hashing;
2527
import com.exonum.binding.common.runtime.ServiceArtifactId;
2628
import com.exonum.binding.core.blockchain.BlockchainData;
2729
import com.exonum.binding.core.service.BlockCommittedEvent;
@@ -66,6 +68,10 @@ public final class ServiceRuntime implements AutoCloseable {
6668
@VisibleForTesting
6769
static final String API_ROOT_PATH = "/api/services";
6870
private static final Logger logger = LogManager.getLogger(ServiceRuntime.class);
71+
@VisibleForTesting static final PublicKey ZERO_PK =
72+
PublicKey.fromBytes(new byte[Ed25519.PUBLIC_KEY_BYTES]);
73+
@VisibleForTesting static final HashCode ZERO_HASH =
74+
HashCode.fromBytes(new byte[Hashing.DEFAULT_HASH_SIZE_BYTES]);
6975

7076
private final ServiceLoader serviceLoader;
7177
private final ServicesFactory servicesFactory;
@@ -200,7 +206,8 @@ public void initiateAddingService(BlockchainData blockchainData, ServiceInstance
200206
ServiceWrapper service = createServiceInstance(instanceSpec);
201207

202208
// Initialize it
203-
service.initialize(blockchainData, new ServiceConfiguration(configuration));
209+
TransactionContext context = newContext(service, blockchainData).build();
210+
service.initialize(context, new ServiceConfiguration(configuration));
204211
}
205212

206213
// Log the initialization event
@@ -234,7 +241,8 @@ public void initiateResumingService(BlockchainData blockchainData,
234241
synchronized (lock) {
235242
checkStoppedService(instanceSpec.getId());
236243
ServiceWrapper service = createServiceInstance(instanceSpec);
237-
service.resume(blockchainData, arguments);
244+
TransactionContext context = newContext(service, blockchainData).build();
245+
service.resume(context, arguments);
238246
}
239247
logger.info("Resumed service: {}", instanceSpec);
240248
} catch (Exception e) {
@@ -383,13 +391,9 @@ public void executeTransaction(int serviceId, String interfaceName, int txId,
383391
PublicKey authorPublicKey) {
384392
synchronized (lock) {
385393
ServiceWrapper service = getServiceById(serviceId);
386-
String serviceName = service.getName();
387-
TransactionContext context = TransactionContext.builder()
388-
.blockchainData(blockchainData)
394+
TransactionContext context = newContext(service, blockchainData)
389395
.txMessageHash(txMessageHash)
390396
.authorPk(authorPublicKey)
391-
.serviceName(serviceName)
392-
.serviceId(serviceId)
393397
.build();
394398
try {
395399
service.executeTransaction(interfaceName, txId, arguments, callerServiceId, context);
@@ -410,7 +414,8 @@ public void beforeTransactions(int serviceId, BlockchainData blockchainData) {
410414
synchronized (lock) {
411415
ServiceWrapper service = getServiceById(serviceId);
412416
try {
413-
service.beforeTransactions(blockchainData);
417+
TransactionContext context = newContext(service, blockchainData).build();
418+
service.beforeTransactions(context);
414419
} catch (Exception e) {
415420
logger.error("Service {} threw exception in beforeTransactions.", service.getName(), e);
416421
throw e;
@@ -435,7 +440,8 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
435440
synchronized (lock) {
436441
ServiceWrapper service = getServiceById(serviceId);
437442
try {
438-
service.afterTransactions(blockchainData);
443+
TransactionContext context = newContext(service, blockchainData).build();
444+
service.afterTransactions(context);
439445
} catch (Exception e) {
440446
logger.error("Service {} threw exception in afterTransactions."
441447
+ " Any changes will be rolled-back", service.getName(), e);
@@ -444,6 +450,17 @@ public void afterTransactions(int serviceId, BlockchainData blockchainData) {
444450
}
445451
}
446452

453+
/** Creates a fully-initialized builder of a 'zero' context for the given service. */
454+
private static TransactionContext.Builder newContext(ServiceWrapper service,
455+
BlockchainData blockchainData) {
456+
return TransactionContext.builder()
457+
.blockchainData(blockchainData)
458+
.serviceName(service.getName())
459+
.serviceId(service.getId())
460+
.txMessageHash(ZERO_HASH)
461+
.authorPk(ZERO_PK);
462+
}
463+
447464
/**
448465
* Notifies the services in the runtime of the block commit event.
449466
* @param snapshot a snapshot of the current database state

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
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;
@@ -102,12 +101,12 @@ int getId() {
102101
return instanceSpec.getId();
103102
}
104103

105-
void initialize(BlockchainData blockchainData, Configuration configuration) {
106-
callServiceMethod(() -> service.initialize(blockchainData, configuration));
104+
void initialize(TransactionContext 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(TransactionContext context, byte[] arguments) {
109+
callServiceMethod(() -> service.resume(context, arguments));
111110
}
112111

113112
void executeTransaction(String interfaceName, int txId, byte[] arguments, int callerServiceId,
@@ -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(TransactionContext context) {
158+
callServiceMethod(() -> service.beforeTransactions(context));
161159
}
162160

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

167165
/**

exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configurable.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.exonum.binding.core.service;
1818

19-
import com.exonum.binding.core.blockchain.BlockchainData;
19+
import com.exonum.binding.core.transaction.TransactionContext;
2020

2121
/**
2222
* A configurable Exonum service. Allows services to update their configuration through
@@ -27,12 +27,11 @@
2727
* and application of the new configuration. The protocol of the proposal and approval steps
2828
* is determined by the installed supervisor service. The verification and application
2929
* of the parameters are implemented by the service with
30-
* {@link #verifyConfiguration(BlockchainData, Configuration)}
31-
* and {@link #applyConfiguration(BlockchainData, Configuration)} methods.
30+
* {@link #verifyConfiguration(TransactionContext, Configuration)}
31+
* and {@link #applyConfiguration(TransactionContext, Configuration)} methods.
3232
*
3333
* <p>Services may use the same configuration parameters as
34-
* in {@link Service#initialize(com.exonum.binding.core.blockchain.BlockchainData, Configuration)},
35-
* or different.
34+
* in {@link Service#initialize(TransactionContext, Configuration)}, or different.
3635
* <!--
3736
* TODO: Link the appropriate documentation section on updating the service configuration
3837
* through the supervisor when it becomes available (ideally, on the site; or in published
@@ -48,22 +47,23 @@ public interface Configurable {
4847
* configuration is correct, this method shall return with no changes to the service data.
4948
* If it is not valid, this method shall throw an exception.
5049
*
51-
* @param blockchainData a read-only access object representing the current database state
50+
* @param context a read-only execution context object, providing access to the current database
51+
* state
5252
* @param configuration a proposed configuration
5353
* @throws com.exonum.binding.core.transaction.ExecutionException if the proposed configuration
5454
* is not valid to prevent the configuration application
5555
*/
56-
void verifyConfiguration(BlockchainData blockchainData, Configuration configuration);
56+
void verifyConfiguration(TransactionContext context, Configuration configuration);
5757

5858
/**
5959
* Applies the given configuration to this service. The configuration is guaranteed to be
60-
* valid according to {@link #verifyConfiguration(BlockchainData, Configuration)}.
60+
* valid according to {@link #verifyConfiguration(TransactionContext, Configuration)}.
6161
*
6262
* <p>The implementation shall make any changes to the service persistent state to apply
6363
* the new configuration, because the supervisor does <em>not</em> store them for later retrieval.
6464
*
65-
* @param blockchainData blockchain data accessor for this service to apply changes to
65+
* @param context an execution context for this service
6666
* @param configuration a new valid configuration
6767
*/
68-
void applyConfiguration(BlockchainData blockchainData, Configuration configuration);
68+
void applyConfiguration(TransactionContext context, Configuration configuration);
6969
}

exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Configuration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@
2727
* <p>Network administrators agree on and pass
2828
* the configuration parameters as a service-specific protobuf message when adding
2929
* that service instance to the network. After Exonum starts the service, it
30-
* {@linkplain Service#initialize(com.exonum.binding.core.blockchain.BlockchainData, Configuration)
31-
* passes the configuration parameters} to the newly created service instance.
30+
* {@linkplain Service#initialize(com.exonum.binding.core.transaction.TransactionContext,
31+
* Configuration) passes the configuration parameters} to the newly created service instance.
3232
*
3333
* <p>Services that have few arguments are encouraged to use the standard protobuf
3434
* message {@link ServiceConfiguration}. It supports common text-based configuration formats.
3535
*
3636
* <p>Reconfiguration of a started service may be implemented with a supervisor service
3737
* and {@link Configurable} interface.
3838
*
39-
* @see Service#initialize(com.exonum.binding.core.blockchain.BlockchainData, Configuration)
39+
* @see Service#initialize(com.exonum.binding.core.transaction.TransactionContext, Configuration)
4040
* @see Configurable
4141
*/
4242
public interface Configuration {

exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Schema.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.exonum.binding.core.service;
1818

1919
import com.exonum.binding.core.blockchain.Block;
20-
import com.exonum.binding.core.blockchain.BlockchainData;
2120
import com.exonum.binding.core.transaction.Transaction;
2221

2322
/**
@@ -33,8 +32,8 @@
3332
* <p>Exonum starts aggregating a service collection state hash once it is <em>initialized</em>:
3433
* created for the first time with a read-write
3534
* {@link com.exonum.binding.core.blockchain.BlockchainData} (e.g., in a
36-
* {@linkplain Service#initialize(BlockchainData, Configuration) service constructor},
37-
* or in a {@linkplain Transaction}).
35+
* {@linkplain Service#initialize(com.exonum.binding.core.transaction.TransactionContext,
36+
* Configuration) service constructor}, or in a {@linkplain Transaction}).
3837
*
3938
* <p>Please note that if the service does not use any Merkelized collections,
4039
* the framework will not be able to verify that its transactions cause the same

exonum-java-binding/core/src/main/java/com/exonum/binding/core/service/Service.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
package com.exonum.binding.core.service;
1818

19-
import com.exonum.binding.core.blockchain.BlockchainData;
2019
import com.exonum.binding.core.transaction.ExecutionException;
20+
import com.exonum.binding.core.transaction.TransactionContext;
2121
import io.vertx.ext.web.Router;
2222

2323
/**
@@ -40,8 +40,7 @@ public interface Service {
4040
* or save all or some configuration parameters as is for later retrieval in transactions
4141
* and/or read requests.
4242
*
43-
* @param blockchainData blockchain data accessor for this service. Not valid after this method
44-
* returns
43+
* @param context the execution context
4544
* @param configuration the service configuration parameters
4645
* @throws ExecutionException if the configuration parameters are not valid (e.g.,
4746
* malformed, or do not meet the preconditions). Exonum will stop the service if
@@ -50,7 +49,7 @@ public interface Service {
5049
* the registry of call errors}
5150
* @see Configurable
5251
*/
53-
default void initialize(BlockchainData blockchainData, Configuration configuration) {
52+
default void initialize(TransactionContext context, Configuration configuration) {
5453
// No configuration
5554
}
5655

@@ -66,13 +65,12 @@ default void initialize(BlockchainData blockchainData, Configuration configurati
6665
* when the block is committed.
6766
* <!--TODO: Add a link to the migration procedure -->
6867
*
69-
* @param blockchainData blockchain data accessor for this service. Not valid after this method
70-
* returns
68+
* @param context the execution context
7169
* @param arguments the service arguments
7270
* @throws ExecutionException if the arguments are not valid (e.g.,
7371
* malformed, or do not meet the preconditions)
7472
*/
75-
default void resume(BlockchainData blockchainData, byte[] arguments) {
73+
default void resume(TransactionContext context, byte[] arguments) {
7674
// No actions by default
7775
}
7876

@@ -108,12 +106,12 @@ default void resume(BlockchainData blockchainData, byte[] arguments) {
108106

109107
/**
110108
* An optional callback method invoked by the blockchain <em>before</em> any transactions
111-
* in a block are executed. See {@link #afterTransactions(BlockchainData)} for details.
109+
* in a block are executed. See {@link #afterTransactions(TransactionContext)} for details.
112110
*
113-
* @see #afterTransactions(BlockchainData)
111+
* @see #afterTransactions(TransactionContext)
114112
* @see com.exonum.binding.core.transaction.Transaction
115113
*/
116-
default void beforeTransactions(BlockchainData blockchainData) {}
114+
default void beforeTransactions(TransactionContext context) {}
117115

118116
/**
119117
* Handles the changes made by all transactions included in the upcoming block.
@@ -131,15 +129,14 @@ default void beforeTransactions(BlockchainData blockchainData) {}
131129
* in {@linkplain com.exonum.binding.core.blockchain.Blockchain#getCallErrors(long)
132130
* the registry of call errors} with appropriate error kinds.
133131
*
134-
* @param blockchainData blockchain data accessor for this service. Not valid after this method
135-
* returns
132+
* @param context the execution context
136133
* @throws ExecutionException if an error occurs during the method execution;
137134
* it is saved as a call error of kind "service". Any other exceptions
138135
* are considered unexpected. They are saved with kind "unexpected".
139-
* @see #beforeTransactions(BlockchainData)
136+
* @see #beforeTransactions(TransactionContext)
140137
* @see com.exonum.binding.core.transaction.Transaction
141138
*/
142-
default void afterTransactions(BlockchainData blockchainData) {}
139+
default void afterTransactions(TransactionContext context) {}
143140

144141
/**
145142
* Handles read-only block commit event. This handler is an optional callback method which is

0 commit comments

Comments
 (0)