All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.scalar.dl.client.service.ClientService Maven / Gradle / Ivy

There is a newer version: 3.10.0
Show newest version
/*
 * 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