com.scalar.dl.client.service.ClientService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalardl-java-client-sdk Show documentation
Show all versions of scalardl-java-client-sdk Show documentation
A client-side Java library to interact with Scalar DL network.
/*
* This file is part of the Scalar DL client SDK.
* Copyright (c) 2019 Scalar, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* You can be released from the requirements of the license by purchasing
* a commercial license. For more information, please contact Scalar, Inc.
*/
package com.scalar.dl.client.service;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.inject.Guice;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.scalar.dl.client.config.ClientConfig;
import com.scalar.dl.client.config.ClientMode;
import com.scalar.dl.client.exception.ClientException;
import com.scalar.dl.client.util.Common;
import com.scalar.dl.client.util.RequestSigner;
import com.scalar.dl.ledger.exception.ValidationException;
import com.scalar.dl.ledger.model.ContractExecutionResult;
import com.scalar.dl.ledger.model.LedgerValidationResult;
import com.scalar.dl.ledger.model.LedgersValidationResult;
import com.scalar.dl.ledger.service.StatusCode;
import com.scalar.dl.rpc.AssetProof;
import com.scalar.dl.rpc.CertificateRegistrationRequest;
import com.scalar.dl.rpc.ContractExecutionRequest;
import com.scalar.dl.rpc.ContractExecutionResponse;
import com.scalar.dl.rpc.ContractRegistrationRequest;
import com.scalar.dl.rpc.ContractsListingRequest;
import com.scalar.dl.rpc.ExecutionOrderingResponse;
import com.scalar.dl.rpc.ExecutionValidationRequest;
import com.scalar.dl.rpc.FunctionRegistrationRequest;
import com.scalar.dl.rpc.LedgerValidationRequest;
import com.scalar.dl.rpc.LedgerValidationResponse;
import com.scalar.dl.rpc.LedgersValidationRequest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
/**
* A thread-safe client that interacts with the ledger components to register certificates, register
* contracts, and execute contracts.
*
* Usage Examples
*
* Here is a simple example to demonstrate how to use them. {@code ClientService} should always be
* initialized with {@link Guice}, which maintains application-level singleton. Using this with a
* try-with-resources statement is also recommended to close it properly.
*
* {@code
* Injector injector = Guice.createInjector(new ClientModule(new ClientConfig(new File(properties))));
*
* try (ClientService service = injector.getInstance(ClientService.class)) {
* JsonObject jsonArgument = Json.createReader(new StringReader(contractArgument)).readObject();
* ContractExecutionResult result = service.executeContract(contractId, jsonArgument);
* result.getResult().ifPresent(System.out::println);
* } catch (ClientException e) {
* System.err.println(e.getStatusCode());
* System.err.println(e.getMessage());
* }
* }
*/
@Immutable
public class ClientService implements AutoCloseable {
private static final String NONCE_KEY_NAME = "nonce";
static final String VALIDATE_LEDGER_ASSET_ID_KEY = "asset_id";
static final String VALIDATE_LEDGER_START_AGE_KEY = "start_age";
static final String VALIDATE_LEDGER_END_AGE_KEY = "end_age";
private static final int NUM_RETRIES_FOR_CLOSE = 3;
private final ClientConfig config;
private final AbstractLedgerClient client;
private final AbstractAuditorClient auditorClient;
private final RequestSigner signer;
/**
* Constructs a {@code ClientService} with the specified {@link ClientConfig}, {@link
* AbstractLedgerClient} and {@link RequestSigner}. This constructor shouldn't be called
* explicitly and should be called implicitly by {@link Guice}.
*
* @param config a configuration for the client
* @param client a client for the ledger server
* @param auditorClient a client for the auditor server
* @param signer a request signer for requests
*/
@Inject
public ClientService(
ClientConfig config,
AbstractLedgerClient client,
@Nullable AbstractAuditorClient auditorClient,
@Nullable RequestSigner signer) {
this.config = config;
this.client = client;
this.auditorClient = auditorClient;
this.signer = signer;
}
/**
* Registers the certificate specified in the given {@code ClientConfig}.
*
* @throws ClientException if a request fails for some reason
*/
public void registerCertificate() {
checkClientMode(ClientMode.CLIENT);
CertificateRegistrationRequest request =
CertificateRegistrationRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion())
.setCertPem(config.getCert())
.build();
registerToAuditor(request);
client.register(request);
}
/**
* Registers the certificate with the specified serialized byte array of a {@code
* CertificateRegistrationRequest}. request.
*
* @param serializedBinary a serialized byte array of {@code CertificateRegistrationRequest}.
* @throws ClientException if a request fails for some reason
*/
public void registerCertificate(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
CertificateRegistrationRequest request;
try {
request = CertificateRegistrationRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
registerToAuditor(request);
client.register(request);
}
/**
* Registers the specified function.
*
* @param id an ID of the function
* @param name the binary name of the function class
* @param functionBytes the byte code of the function class
* @throws ClientException if a request fails for some reason
*/
public void registerFunction(String id, String name, byte[] functionBytes) {
checkClientMode(ClientMode.CLIENT);
checkArgument(id != null, "id cannot be null");
checkArgument(name != null, "name cannot be null");
checkArgument(functionBytes != null, "functionBytes cannot be null");
FunctionRegistrationRequest request =
FunctionRegistrationRequest.newBuilder()
.setFunctionId(id)
.setFunctionBinaryName(name)
.setFunctionByteCode(ByteString.copyFrom(functionBytes))
.build();
client.register(request);
}
/**
* Registers the specified function.
*
* @param id an ID of the function
* @param name the binary name of the function class
* @param functionPath the relative path of the function class
* @throws ClientException if a request fails for some reason
*/
public void registerFunction(String id, String name, String functionPath) {
checkClientMode(ClientMode.CLIENT);
checkArgument(id != null, "id cannot be null");
checkArgument(name != null, "name cannot be null");
checkArgument(functionPath != null, "functionPath cannot be null");
byte[] functionBytes = Common.fileToBytes(functionPath);
registerFunction(id, name, functionBytes);
}
/**
* Registers the function with the specified serialized byte array of a {@code
* FunctionRegistrationRequest}.
*
* @param serializedBinary a serialized byte array of {@code FunctionRegistrationRequest}.
* @throws ClientException if a request fails for some reason
*/
public void registerFunction(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
FunctionRegistrationRequest request;
try {
request = FunctionRegistrationRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
client.register(request);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
*
* @param id an ID of the contract
* @param name the binary name of the contract class
* @param contractBytes the byte code of the contract class
* @param properties a contract properties
* @throws ClientException if a request fails for some reason
*/
public void registerContract(
String id, String name, byte[] contractBytes, Optional properties) {
checkClientMode(ClientMode.CLIENT);
checkArgument(id != null, "id cannot be null");
checkArgument(name != null, "name cannot be null");
checkArgument(contractBytes != null, "contractBytes cannot be null");
ContractRegistrationRequest.Builder builder =
ContractRegistrationRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion())
.setContractId(id)
.setContractBinaryName(name)
.setContractByteCode(ByteString.copyFrom(contractBytes));
properties.ifPresent(p -> builder.setContractProperties(p.toString()));
ContractRegistrationRequest request = signer.sign(builder).build();
registerToAuditor(request);
client.register(request);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
*
* @param id an ID for the contract
* @param name the binary name of the contract class
* @param contractPath the relative path of the contract class
* @param properties a contract properties
* @throws ClientException if a request fails for some reason
*/
public void registerContract(
String id, String name, String contractPath, Optional properties) {
checkClientMode(ClientMode.CLIENT);
checkArgument(id != null, "id cannot be null");
checkArgument(name != null, "name cannot be null");
checkArgument(contractPath != null, "contractPath cannot be null");
byte[] contractBytes = Common.fileToBytes(contractPath);
registerContract(id, name, contractBytes, properties);
}
/**
* Registers the contract with the specified serialized byte array of a {@code
* ContractRegistrationRequest}.
*
* @param serializedBinary a serialized byte array of {@code ContractRegistrationRequest}.
* @throws ClientException if a request fails for some reason
*/
public void registerContract(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
ContractRegistrationRequest request;
try {
request = ContractRegistrationRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
registerToAuditor(request);
client.register(request);
}
/**
* Retrieves a list of contracts for the certificate holder specified in {@code ClientConfig}. If
* specified with a contract ID, it will return the matching contract only.
*
* @param id a contract ID
* @return {@link JsonObject}
* @throws ClientException if a request fails for some reason
*/
public JsonObject listContracts(String id) {
checkClientMode(ClientMode.CLIENT);
ContractsListingRequest.Builder builder =
ContractsListingRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion());
if (id != null) {
builder.setContractId(id);
}
ContractsListingRequest request = signer.sign(builder).build();
return client.list(request);
}
/**
* Retrieves a list of contracts with the specified serialized byte array of a {@code
* ContractsListingRequest}.
*
* @param serializedBinary a serialized byte array of {@code ContractsListingRequest}.
* @return {@link JsonObject}
* @throws ClientException if a request fails for some reason
*/
public JsonObject listContracts(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
ContractsListingRequest request;
try {
request = ContractsListingRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
return client.list(request);
}
/**
* Executes the specified contract with the specified argument for the certificate holder
* specified in {@code ClientConfig}.
*
* @param id an ID of the contract
* @param argument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(String id, JsonObject argument) {
return executeContract(id, argument, Optional.empty());
}
/**
* Executes the specified contract with the specified argument for the certificate holder
* specified in {@code ClientConfig}.
*
* @param id an ID of the contract
* @param argument an argument of the contract
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String id, JsonObject argument, Optional functionArgument) {
checkClientMode(ClientMode.CLIENT);
checkArgument(id != null, "id cannot be null");
checkArgument(argument != null, "argument cannot be null");
checkArgument(functionArgument != null, "functionArgument cannot be null");
argument = createArgumentWithNonce(argument);
ContractExecutionRequest.Builder builder =
ContractExecutionRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion())
.setContractId(id)
.setContractArgument(argument.toString());
functionArgument.ifPresent(arg -> builder.setFunctionArgument(arg.toString()));
ContractExecutionRequest request = signer.sign(builder).build();
ContractExecutionRequest ordered = order(request);
return client.execute(ordered, r -> validate(ordered, r));
}
/**
* Executes the specified contract with the specified serialized byte array of a {@code
* ContractExecutionRequest}.
*
* @param serializedBinary a serialized byte array of {@code ContractExecutionRequest}.
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
ContractExecutionRequest request;
try {
request = ContractExecutionRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
ContractExecutionRequest ordered = order(request);
return client.execute(ordered, r -> validate(ordered, r));
}
/**
* Validates the specified asset in the ledger for the certificate holder specified in {@code
* ClientConfig}.
*
* @param assetId an asset ID
* @return {@link LedgerValidationResult}
* @throws ClientException if a request fails for some reason
*/
public LedgerValidationResult validateLedger(String assetId) {
return validateLedger(assetId, 0, Integer.MAX_VALUE);
}
/**
* Validates the specified asset between the specified ages in the ledger for the certificate
* holder specified in {@code ClientConfig}.
*
* @param assetId an asset ID
* @param startAge an age to be validated from (inclusive)
* @param endAge an age to be validated to (inclusive)
* @return {@link LedgerValidationResult}
* @throws ClientException if a request fails for some reason
*/
public LedgerValidationResult validateLedger(String assetId, int startAge, int endAge) {
checkClientMode(ClientMode.CLIENT);
checkArgument(assetId != null, "assetId cannot be null");
checkArgument(endAge >= startAge && startAge >= 0, "invalid ages specified");
if (config.isAuditorEnabled() && config.isAuditorLinearizableValidationEnabled()) {
return validateLedgerWithContractExecution(assetId, startAge, endAge);
} else {
LedgerValidationRequest.Builder builder =
LedgerValidationRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion())
.setAssetId(assetId)
.setStartAge(startAge)
.setEndAge(endAge);
LedgerValidationRequest request = signer.sign(builder).build();
Future future = validateLedgerAsync(request);
LedgerValidationResult ledgerResult = client.validate(request);
LedgerValidationResult auditorResult = validateLedgerAwait(future);
return validate(ledgerResult, auditorResult);
}
}
/**
* Validates the specified asset in the ledger with the specified serialized byte array of a
* {@code LedgerValidationRequest}.
*
* @param serializedBinary a serialized byte array of {@code LedgerValidationRequest}.
* @return {@link LedgerValidationResult}
* @throws ClientException if a request fails for some reason
*/
public LedgerValidationResult validateLedger(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
if (config.isAuditorEnabled() && config.isAuditorLinearizableValidationEnabled()) {
throw new UnsupportedOperationException(
"Linearizable validation with validateLedger is not supported in the intermediary mode. "
+ "Please execute ValidateLedger contract simply for validating assets.");
}
LedgerValidationRequest request;
try {
request = LedgerValidationRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
Future future = validateLedgerAsync(request);
LedgerValidationResult ledgerResult = client.validate(request);
LedgerValidationResult auditorResult = validateLedgerAwait(future);
return validate(ledgerResult, auditorResult);
}
/**
* Validates the specified asset in multiple ledgers for the certificate holder specified in
* {@code ClientConfig}.
*
* @param assetId an asset ID
* @return {@link LedgersValidationResult}
* @throws ClientException if a request fails for some reason
*/
public LedgersValidationResult validateLedgers(String assetId) {
checkClientMode(ClientMode.CLIENT);
checkArgument(assetId != null, "assetId cannot be null");
LedgersValidationRequest.Builder builder =
LedgersValidationRequest.newBuilder()
.setCertHolderId(config.getCertHolderId())
.setCertVersion(config.getCertVersion())
.setAssetId(assetId);
LedgersValidationRequest request = signer.sign(builder).build();
return client.validate(request);
}
/**
* Validates the specified asset in multiple ledgers with the specified serialized byte array of a
* {@code LedgersValidationRequest}.
*
* @param serializedBinary a serialized byte array of {@code LedgersValidationRequest}.
* @return {@link LedgersValidationResult}
* @throws ClientException if a request fails for some reason
*/
public LedgersValidationResult validateLedgers(byte[] serializedBinary) {
checkClientMode(ClientMode.INTERMEDIARY);
LedgersValidationRequest request;
try {
request = LedgersValidationRequest.parseFrom(serializedBinary);
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
return client.validate(request);
}
/** Releases resources such as TCP connections. */
@Override
public void close() {
for (int i = 0; i < NUM_RETRIES_FOR_CLOSE; ++i) {
try {
client.shutdown();
return;
} catch (Exception e) {
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
}
}
private void checkClientMode(ClientMode expected) {
checkArgument(config.getClientMode().equals(expected), "wrong mode specified.");
}
private JsonObject createArgumentWithNonce(JsonObject argument) {
if (!argument.containsKey(NONCE_KEY_NAME) || argument.getString(NONCE_KEY_NAME).isEmpty()) {
String nonce = UUID.randomUUID().toString();
return Json.createObjectBuilder(argument).add(NONCE_KEY_NAME, nonce).build();
}
return argument;
}
private void registerToAuditor(CertificateRegistrationRequest request) {
if (!config.isAuditorEnabled()) {
return;
}
try {
auditorClient.register(request);
} catch (ClientException e) {
if (!e.getStatusCode().equals(StatusCode.CERTIFICATE_ALREADY_REGISTERED)) {
throw e;
}
}
}
private void registerToAuditor(ContractRegistrationRequest request) {
if (!config.isAuditorEnabled()) {
return;
}
try {
auditorClient.register(request);
} catch (ClientException e) {
if (!e.getStatusCode().equals(StatusCode.CONTRACT_ALREADY_REGISTERED)) {
throw e;
}
}
}
private ContractExecutionRequest order(ContractExecutionRequest request) {
if (!config.isAuditorEnabled()) {
return request;
}
ExecutionOrderingResponse response = auditorClient.order(request);
return ContractExecutionRequest.newBuilder(request)
.setAuditorSignature(response.getSignature())
.build();
}
private ContractExecutionResponse validate(
ContractExecutionRequest request, ContractExecutionResponse ledgerResponse) {
if (!config.isAuditorEnabled()) {
return null;
}
ExecutionValidationRequest req =
ExecutionValidationRequest.newBuilder()
.setRequest(request)
.addAllProofs(ledgerResponse.getProofsList())
.build();
ContractExecutionResponse auditorResponse = auditorClient.validate(req);
validateResponses(ledgerResponse, auditorResponse);
return auditorResponse;
}
private void validateResponses(
ContractExecutionResponse ledgerResponse, ContractExecutionResponse auditorResponse) {
Runnable throwError =
() -> {
throw new ValidationException(
"The results from Ledger and Auditor don't match", StatusCode.INCONSISTENT_STATES);
};
if (!ledgerResponse.getResult().equals(auditorResponse.getResult())
|| ledgerResponse.getProofsCount() != auditorResponse.getProofsCount()) {
throwError.run();
}
Map map = new HashMap<>();
ledgerResponse.getProofsList().forEach(p -> map.put(p.getAssetId(), p));
auditorResponse
.getProofsList()
.forEach(
p2 -> {
AssetProof p1 = map.get(p2.getAssetId());
if (p1 == null || p1.getAge() != p2.getAge() || !p1.getHash().equals(p2.getHash())) {
throwError.run();
}
});
}
private Future validateLedgerAsync(LedgerValidationRequest request) {
if (config.isAuditorEnabled()) {
return auditorClient.validateAsync(request);
}
return CompletableFuture.completedFuture(LedgerValidationResponse.getDefaultInstance());
}
private LedgerValidationResult validateLedgerAwait(Future future) {
if (config.isAuditorEnabled()) {
return auditorClient.validateAwait(future);
}
return null;
}
private LedgerValidationResult validate(
LedgerValidationResult ledgerResult, LedgerValidationResult auditorResult) {
if (config.isAuditorEnabled()) {
StatusCode code = StatusCode.INCONSISTENT_STATES;
if (ledgerResult != null
&& auditorResult != null
&& ledgerResult.getCode().equals(StatusCode.OK)
&& auditorResult.getCode().equals(StatusCode.OK)
&& ledgerResult.getProof().isPresent()
&& auditorResult.getProof().isPresent()
&& Arrays.equals(
ledgerResult.getProof().get().getHash(), auditorResult.getProof().get().getHash())) {
code = StatusCode.OK;
}
return new LedgerValidationResult(
code, ledgerResult.getProof().orElse(null), auditorResult.getProof().orElse(null));
} else {
return ledgerResult;
}
}
private LedgerValidationResult validateLedgerWithContractExecution(
String assetId, int startAge, int endAge) {
JsonObjectBuilder argumentBuilder =
Json.createObjectBuilder().add(VALIDATE_LEDGER_ASSET_ID_KEY, assetId);
argumentBuilder.add(VALIDATE_LEDGER_START_AGE_KEY, startAge);
argumentBuilder.add(VALIDATE_LEDGER_END_AGE_KEY, endAge);
ContractExecutionResult result =
executeContract(
config.getAuditorLinearizableValidationContractId(), argumentBuilder.build());
return new LedgerValidationResult(
StatusCode.OK, result.getProofs().get(0), result.getAuditorProofs().get(0));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy