Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ void destroyTestkit() {
testKit.close();
}

// todo: Add tests [ECR-3597]:
// - no service with the requested name
// - a service with the given name is not a time oracle

@Test
void getTime() {
setUpConsolidatedTime();
Expand Down Expand Up @@ -98,7 +102,8 @@ private void setUpConsolidatedTime() {

private void testKitTest(Consumer<TimeSchema> test) {
Snapshot view = testKit.getSnapshot();
TimeSchema timeSchema = TimeSchema.newInstance(view);
// Fixme: specify name [ECR-3597]
TimeSchema timeSchema = TimeSchema.newInstance(view, "");
test.accept(timeSchema);
}

Expand Down
8 changes: 8 additions & 0 deletions exonum-java-binding/time-oracle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<configLocation>${project.parent.basedir}/../checkstyle.xml</configLocation>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,31 @@

/**
* Exonum time service database schema. It provides read-only access to the state
* of the time oracle for a given {@linkplain View database view}.
* of a time oracle service instance.
*
* @see <a href="https://exonum.com/doc/version/0.12/advanced/time/">Time oracle documentation</a>
*/
public interface TimeSchema {

/**
* Constructs a time schema for a given dbView.
* Constructs a schema of the time oracle instance with the given name.
*
* <p>Won't be constructed unless time service is enabled. To enable time service, put 'time'
* into 'services.toml' file.
* @param dbView the database view
* @param name the name of the time oracle service instance to use
*
* @throws IllegalStateException if time service is not enabled
* @throws IllegalArgumentException if there is no service with the given name or it is not
* an Exonum time oracle
*/
static TimeSchema newInstance(View dbView) {
return new TimeSchemaProxy(dbView);
static TimeSchema newInstance(View dbView, String name) {
return new TimeSchemaProxy(dbView, name);
}

/**
* Returns consolidated time output by the service, which can be used by other business logic on
* the blockchain.
* Returns consolidated time output by the service in UTC.
*
* <p>At the time when a new blockchain is launched, the consolidated time is unknown until the
* transactions from at least two thirds of validator nodes are processed. In that case the result
* will not contain a value.
* <p>When this time oracle instance is started, the consolidated time remains unknown until
* the transactions with time updates from at least two thirds of validator nodes are processed.
* After that the time will be always present.
*/
EntryIndexProxy<ZonedDateTime> getTime();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,76 @@

package com.exonum.binding.time;

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

import com.exonum.binding.common.crypto.PublicKey;
import com.exonum.binding.common.serialization.Serializer;
import com.exonum.binding.common.serialization.StandardSerializers;
import com.exonum.binding.core.runtime.DispatcherSchema;
import com.exonum.binding.core.runtime.RuntimeId;
import com.exonum.binding.core.storage.database.View;
import com.exonum.binding.core.storage.indices.EntryIndexProxy;
import com.exonum.binding.core.storage.indices.MapIndex;
import com.exonum.binding.core.storage.indices.ProofMapIndexProxy;
import com.exonum.binding.core.util.LibraryLoader;
import com.exonum.binding.messages.Runtime.ArtifactId;
import com.exonum.binding.messages.Runtime.InstanceSpec;
import java.time.ZonedDateTime;

class TimeSchemaProxy implements TimeSchema {

static {
LibraryLoader.load();
}
private static final int RUST_RUNTIME_ID = RuntimeId.RUST.getId();
private static final String EXONUM_TIME_ARTIFACT_NAME_PREFIX = "exonum-time:";

private static final Serializer<PublicKey> PUBLIC_KEY_SERIALIZER =
StandardSerializers.publicKey();
private static final Serializer<ZonedDateTime> ZONED_DATE_TIME_SERIALIZER =
UtcZonedDateTimeSerializer.INSTANCE;

private final View dbView;
private final View view;
private final String name;

TimeSchemaProxy(View dbView) {
TimeSchemaProxy(View view, String name) {
this.name = name;
this.view = view;
checkIfEnabled();
this.dbView = dbView;
}

private void checkIfEnabled() {
MapIndex<String, InstanceSpec> serviceInstances = new DispatcherSchema(view).serviceInstances();
checkArgument(serviceInstances.containsKey(name), "No service instance "
+ "with the given name (%s) started.", name);

InstanceSpec serviceSpec = serviceInstances.get(name);
ArtifactId artifactId = serviceSpec.getArtifact();
checkArgument(isTimeOracleInstance(artifactId), "Service with the given name (%s) is not "
+ "an Exonum time oracle, but %s.", name, artifactId);
}

private static boolean isTimeOracleInstance(ArtifactId artifactId) {
return artifactId.getRuntimeId() == RUST_RUNTIME_ID
&& artifactId.getName().startsWith(EXONUM_TIME_ARTIFACT_NAME_PREFIX);
}

@Override
public EntryIndexProxy<ZonedDateTime> getTime() {
return EntryIndexProxy.newInstance(TimeIndex.TIME, dbView, ZONED_DATE_TIME_SERIALIZER);
return EntryIndexProxy.newInstance(indexName(TimeIndex.TIME), view, ZONED_DATE_TIME_SERIALIZER);
}

@Override
public ProofMapIndexProxy<PublicKey, ZonedDateTime> getValidatorsTimes() {
return ProofMapIndexProxy.newInstance(TimeIndex.VALIDATORS_TIMES, dbView, PUBLIC_KEY_SERIALIZER,
ZONED_DATE_TIME_SERIALIZER);
return ProofMapIndexProxy.newInstance(indexName(TimeIndex.VALIDATORS_TIMES), view,
PUBLIC_KEY_SERIALIZER, ZONED_DATE_TIME_SERIALIZER);
}

private void checkIfEnabled() {
// Skip if invoked in tests because the check relies on the state unavailable
// in integration tests. To be removed in ECR-2970 (Java Testkit)
if (!runningUnitTests()) {
checkState(isTimeServiceEnabled(), "Time service is not enabled. To enable it, put 'time' "
+ "into 'services.toml' file.\n"
+ "See https://exonum.com/doc/version/0.12/get-started/java-binding/#built-in-services "
+ "for details.");
}
private String indexName(String simpleName) {
return name + "." + simpleName;
}

private static boolean runningUnitTests() {
try {
Class.forName("org.junit.jupiter.api.Test");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}

private static native boolean isTimeServiceEnabled();

/**
* Mapping for Exonum time indexes by name.
*/
private static final class TimeIndex {
private static final String PREFIX = "exonum_time.";
private static final String VALIDATORS_TIMES = PREFIX + "validators_times";
private static final String TIME = PREFIX + "time";
private static final String VALIDATORS_TIMES = "validators_times";
private static final String TIME = "time";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@

import com.exonum.binding.common.serialization.Serializer;
import com.exonum.binding.test.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

class UtcZonedDateTimeSerializerTest {

private final Serializer<ZonedDateTime> SERIALIZER = UtcZonedDateTimeSerializer.INSTANCE;
private static final Serializer<ZonedDateTime> SERIALIZER = UtcZonedDateTimeSerializer.INSTANCE;

@ParameterizedTest
@MethodSource("testSource")
Expand Down