Skip to content
Merged
14 changes: 13 additions & 1 deletion exonum-java-binding/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- [`Blockchain`][blockchain-proofs].
- `ProofEntryIndexProxy` collection.
- Transaction precondition utility methods,
see `com.exonum.binding.core.transaction.ExecutionPreconditions`.(#1351)
see `com.exonum.binding.core.transaction.ExecutionPreconditions`. (#1351)
- `supervisor-mode` CLI parameter added for `generate-template` command. It
allows to configure the mode of the Supervisor service. Possible values are
"simple" and "decentralized". (#1361)
- Support of service instances lifecycle: they can be activated, stopped and resumed now.
Also, service instance artifacts can be upgraded before resuming which allows services
API update, add new service transactions, synchronous data migration etc. (#1358, #1372)
- `BlockchainData` — an object providing access to all categories of persistent data
stored in the database: the executing service data, other services’ data,
Exonum Core data. BlockchainData is a service-specific object — it remembers
the service to which it is provided and allows modification only to the data
of that service. The service data is automatically isolated via namespaces,
with a service name followed by a dot as a prefix
(see `BlockchainData#getExecutingServiceData`). (#1393)
- `Prefixed` and `ReadonlyFork` database Accesses. (#1382, #1385)


[blockchain-proofs]: https://exonum.com/doc/api/java-binding/0.10.0-SNAPSHOT/com/exonum/binding/core/blockchain/Blockchain.html#proofs

Expand Down Expand Up @@ -100,6 +109,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
explicitly with `StandardSerializers.protobuf`.
- To create _index groups_ (aka families), pass a *group address*:
`IndexAddress.valueOf(String, byte[])`. (#1374)
- `AbstractService#createDataSchema` — just use the schema constructor/factory
method, as passing the instance name is no longer required with the `Prefixed`
DB Access. (#1393)

## 0.9.0-rc2 - 2019-12-17

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.exonum.binding.core.blockchain.proofs.IndexProof;
import com.exonum.binding.core.service.Configuration;
import com.exonum.binding.core.storage.database.Access;
import com.exonum.binding.core.storage.database.Fork;
import com.exonum.binding.core.storage.database.Snapshot;
import com.exonum.binding.core.storage.indices.KeySetIndexProxy;
import com.exonum.binding.core.storage.indices.ListIndex;
Expand Down Expand Up @@ -196,7 +195,8 @@ public BlockProof createBlockProof(long blockHeight) {
* with a {@link com.exonum.binding.core.storage.database.Fork}. Depending on the service
* 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(Fork, Configuration)}.
* (e.g., in {@link
* com.exonum.binding.core.service.Service#initialize(BlockchainData, Configuration)}.
* <!-- TODO: Simplify once initialization happens automatically: ECR-4121 -->
*/
public IndexProof createIndexProof(String fullIndexName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.exonum.binding.core.proxy.NativeHandle;
import com.exonum.binding.core.proxy.ProxyDestructor;
import com.exonum.binding.core.runtime.DispatcherSchema;
import com.exonum.binding.core.runtime.ServiceInstanceSpec;
import com.exonum.binding.core.storage.database.AbstractAccess;
import com.exonum.binding.core.storage.database.Prefixed;
import com.exonum.binding.core.storage.database.ReadonlyFork;
Expand Down Expand Up @@ -79,12 +80,12 @@ public static BlockchainData fromHandle(long bdNativeHandle, Cleaner cleaner) {
* Creates a BlockchainData for the service with the given name.
*
* @param baseAccess the base database access, must be a "RawAccess"
* @param instanceName a service instance name
* @param serviceName a service instance name
*/
@VisibleForTesting
public static BlockchainData fromRawAccess(AbstractAccess baseAccess, String instanceName) {
public static BlockchainData fromRawAccess(AbstractAccess baseAccess, String serviceName) {
Cleaner cleaner = baseAccess.getCleaner();
long bdNativeHandle = nativeCreate(baseAccess.getAccessNativeHandle(), instanceName);
long bdNativeHandle = nativeCreate(baseAccess.getAccessNativeHandle(), serviceName);
return fromHandleInternal(bdNativeHandle, cleaner);
}

Expand All @@ -110,10 +111,12 @@ private static BlockchainData fromHandleInternal(long bdNativeHandle, Cleaner cl
*
* <p>Only service data is accessible through the returned access. All indexes, initialized
* through this access, are created in a namespace, separate from other services.
* The namespace is equal to the executing service {@linkplain ServiceInstanceSpec#getName()
* name}.
*/
public Prefixed getExecutingServiceData() {
// Since the base access (Fork) is unknown in the main use-case (BlockchainData
// received from core), we must create the Prefixed access at most once so that index
// received from core), we must create the Prefixed access at most once so that index
// pooling works for read-write-based Accesses.
if (executingServiceAccess == null) {
long nativeHandle = getNativeHandle();
Expand All @@ -135,10 +138,12 @@ public Prefixed getExecutingServiceData() {
*
* <p>Only service data is accessible through the returned access.
*
* @param instanceName the name of the service instance to which data to provide access
* <p>The namespace is equal to the service instance name.
*
* @param serviceName the name of the service instance to which data to provide access
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the sentence looks complicated a little bit.
"service instance name for providing data access to"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original seems to be more exact, but I agree it is a little complex, though can't think how to simplify it.

*/
public Optional<Prefixed> findServiceData(String instanceName) {
long prefixedHandle = nativeFindServiceData(getNativeHandle(), instanceName);
public Optional<Prefixed> findServiceData(String serviceName) {
long prefixedHandle = nativeFindServiceData(getNativeHandle(), serviceName);
if (prefixedHandle == NativeHandle.INVALID_NATIVE_HANDLE) {
return Optional.empty();
} else {
Expand All @@ -151,7 +156,7 @@ public Optional<Prefixed> findServiceData(String instanceName) {
* Returns a valid handle to a Prefixed access for the data of service with the given name;
* or 0 (nullptr) if no such service exists.
*/
private static native long nativeFindServiceData(long bdNativeHandle, String instanceName);
private static native long nativeFindServiceData(long bdNativeHandle, String serviceName);

/**
* Returns the blockchain schema (aka Exonum core schema).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.exonum.binding.core.runtime;

import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.proxy.Cleaner;
import com.exonum.binding.core.storage.database.Fork;
import com.exonum.binding.core.storage.database.Snapshot;
Expand All @@ -32,7 +33,6 @@ public interface AccessFactory {
*
* @param nativeHandle a handle to the native snapshot object
* @param cleaner a cleaner to register the destructor
* @return a new owning snapshot proxy
*/
Snapshot createSnapshot(long nativeHandle, Cleaner cleaner);

Expand All @@ -41,7 +41,14 @@ public interface AccessFactory {
*
* @param nativeHandle a handle to the native fork object
* @param cleaner a cleaner to register the destructor
* @return a new owning fork proxy
*/
Fork createFork(long nativeHandle, Cleaner cleaner);

/**
* Creates a new owning blockchain data.
*
* @param nativeHandle a handle to the native BlockchainData object
* @param cleaner a cleaner to register the destructor
*/
BlockchainData createBlockchainData(long nativeHandle, Cleaner cleaner);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.exonum.binding.core.runtime;

import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.proxy.Cleaner;
import com.exonum.binding.core.storage.database.Fork;
import com.exonum.binding.core.storage.database.Snapshot;
Expand All @@ -42,4 +43,9 @@ public Snapshot createSnapshot(long nativeHandle, Cleaner cleaner) {
public Fork createFork(long nativeHandle, Cleaner cleaner) {
return Fork.newInstance(nativeHandle, cleaner);
}

@Override
public BlockchainData createBlockchainData(long nativeHandle, Cleaner cleaner) {
return BlockchainData.fromHandle(nativeHandle, cleaner);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020 The Exonum Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.exonum.binding.core.runtime;

import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.storage.database.AbstractAccess;

/**
* A factory of {@link BlockchainData}.
*
* <p>Enables easier unit testing of the {@link ServiceRuntime} and {@link ServiceNodeProxy}.
*/
interface BlockchainDataFactory {

/**
* Creates a BlockchainData for the service with the given name.
*
* @see BlockchainData#fromRawAccess(AbstractAccess, String)
*/
default BlockchainData fromRawAccess(AbstractAccess rawAccess, String serviceName) {
return BlockchainData.fromRawAccess(rawAccess, serviceName);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2019 The Exonum Team
* Copyright 2020 The Exonum Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.exonum.binding.core.service;
package com.exonum.binding.core.runtime;

import com.exonum.binding.common.crypto.PublicKey;
import com.exonum.binding.common.hash.HashCode;
Expand All @@ -32,7 +32,7 @@
* An Exonum node context. Allows to add transactions to Exonum network
* and get a snapshot of the database state.
*/
public final class NodeProxy extends AbstractCloseableNativeProxy implements Node {
public final class NodeProxy extends AbstractCloseableNativeProxy {

static {
LibraryLoader.load();
Expand All @@ -49,7 +49,6 @@ public NodeProxy(long nativeHandle) {
super(nativeHandle, false);
}

@Override
public HashCode submitTransaction(RawTransaction rawTransaction) {
byte[] payload = rawTransaction.getPayload();
int serviceId = rawTransaction.getServiceId();
Expand All @@ -71,7 +70,6 @@ public HashCode submitTransaction(RawTransaction rawTransaction) {
private static native byte[] nativeSubmit(long nodeHandle, byte[] payload, int serviceId,
int transactionId);

@Override
public <ResultT> ResultT withSnapshot(Function<Snapshot, ResultT> snapshotFunction) {
try (Cleaner cleaner = new Cleaner("NodeProxy#withSnapshot")) {
long nodeNativeHandle = getNativeHandle();
Expand All @@ -86,7 +84,6 @@ public <ResultT> ResultT withSnapshot(Function<Snapshot, ResultT> snapshotFuncti

private native long nativeCreateSnapshot(long nativeHandle);

@Override
public PublicKey getPublicKey() {
byte[] publicKey = nativeGetPublicKey(getNativeHandle());
return PublicKey.fromBytes(publicKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected void configure() {
.toInstance(dependencyReferenceClasses);
bind(ServiceLoader.class).to(Pf4jServiceLoader.class);
bind(ServicesFactory.class).to(GuiceServicesFactory.class);
bind(BlockchainDataFactory.class).toInstance(new BlockchainDataFactory() {});
bind(PluginManager.class).to(JarPluginManager.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@

import com.exonum.binding.common.crypto.PublicKey;
import com.exonum.binding.common.hash.HashCode;
import com.exonum.binding.core.blockchain.BlockchainData;
import com.exonum.binding.core.service.Node;
import com.exonum.binding.core.storage.database.Snapshot;
import com.exonum.binding.core.transaction.RawTransaction;
import java.util.function.Function;

/**
* Node decorator which can restrict an access to the node by calling {@link #close()} method.
* NodeProxy adapter for a particular service.
*/
class MultiplexingNodeDecorator implements Node {
class ServiceNodeProxy implements Node {

private final Node node;
private final NodeProxy node;
private final BlockchainDataFactory blockchainDataFactory;
private final String instanceName;
private boolean closed;

MultiplexingNodeDecorator(Node node) {
ServiceNodeProxy(NodeProxy node, BlockchainDataFactory blockchainDataFactory,
String instanceName) {
this.node = checkNotNull(node);
this.blockchainDataFactory = blockchainDataFactory;
this.instanceName = instanceName;
this.closed = false;
}

Expand All @@ -45,8 +50,9 @@ public HashCode submitTransaction(RawTransaction rawTransaction) {
}

@Override
public <ResultT> ResultT withSnapshot(Function<Snapshot, ResultT> snapshotFunction) {
return node().withSnapshot(snapshotFunction);
public <ResultT> ResultT withBlockchainData(Function<BlockchainData, ResultT> snapshotFunction) {
return node().withSnapshot(snapshotFunction
.compose(snapshot -> blockchainDataFactory.fromRawAccess(snapshot, instanceName)));
}

@Override
Expand All @@ -56,15 +62,15 @@ public PublicKey getPublicKey() {

/**
* Closes an access to the node. After calling this method subsequent calling
* {@link #submitTransaction(RawTransaction)} or {@link #withSnapshot(Function)} methods
* {@link #submitTransaction(RawTransaction)} or {@link #withBlockchainData(Function)} methods
* will cause {@link IllegalStateException}.
*/
@Override
public void close() {
this.closed = true;
}

private Node node() {
private NodeProxy node() {
checkState(!closed, "Node access is closed");
return node;
}
Expand Down
Loading