com.scalar.dl.client.service.ClientService Maven / Gradle / Ivy
Show all versions of scalardl-java-client-sdk Show documentation
/*
* 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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
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.ledger.util.Argument;
import com.scalar.dl.ledger.util.JacksonSerDe;
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.LedgersValidationRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
/**
* A thread-safe client that interacts with Ledger and Auditor components to register certificates,
* register contracts, execute contracts, and validate data.
*
* Usage Examples
*
* Here is a simple example to demonstrate how to use {@code ClientService}. {@code ClientService}
* should always be created with {@link ClientServiceFactory}, which reuses internal instances as
* much as possible for better performance and less resource usage.
*
* {@code
* ClientServiceFactory factory = new ClientServiceFactory(); // the factory should be reused
*
* ClientService service = factory.create(new ClientConfig(new File(properties));
* try {
* JsonNode jsonArgument = ...; // create an application-specific argument
* ContractExecutionResult result = service.executeContract(contractId, jsonArgument);
* result.getContractResult().ifPresent(System.out::println);
* } catch (ClientException e) {
* System.err.println(e.getStatusCode());
* System.err.println(e.getMessage());
* }
*
* factory.close();
* }
*/
@Immutable
public class ClientService implements AutoCloseable {
private static final JacksonSerDe jacksonSerDe = new JacksonSerDe(new ObjectMapper());
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 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
*/
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.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().getCertVersion())
.setCertPem(config.getIdentityConfig().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. To execute the function, you need to use the corresponding
* {@code executeContract} methods that match the type of the 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. To execute the function, you need to use the corresponding
* {@code executeContract} methods that match the type of the 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}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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
* @deprecated This method will be removed in release 5.0.0.
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public void registerContract(
String id, String name, byte[] contractBytes, Optional properties) {
registerContract(id, name, contractBytes, properties.map(Object::toString).orElse(null));
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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
* @deprecated This method will be removed in release 5.0.0.
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public void registerContract(
String id, String name, String contractPath, Optional properties) {
registerContract(id, name, contractPath, properties.map(Object::toString).orElse(null));
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @param id an ID for the contract
* @param name the binary name of the contract class
* @param contractBytes the byte array of the contract class
* @throws ClientException if a request fails for some reason
*/
public void registerContract(String id, String name, byte[] contractBytes) {
registerContract(id, name, contractBytes, (String) null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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
* @throws ClientException if a request fails for some reason
*/
public void registerContract(String id, String name, String contractPath) {
registerContract(id, name, contractPath, (String) null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @param id an ID for the contract
* @param name the binary name of the contract class
* @param contractBytes the byte array 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, @Nullable JsonObject properties) {
registerContract(id, name, contractBytes, properties != null ? properties.toString() : null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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, @Nullable JsonObject properties) {
registerContract(id, name, contractPath, properties != null ? properties.toString() : null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @param id an ID for the contract
* @param name the binary name of the contract class
* @param contractBytes the byte array 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, @Nullable JsonNode properties) {
registerContract(
id, name, contractBytes, properties != null ? jacksonSerDe.serialize(properties) : null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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, @Nullable JsonNode properties) {
registerContract(
id, name, contractPath, properties != null ? jacksonSerDe.serialize(properties) : null);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @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, @Nullable String properties) {
checkArgument(contractPath != null, "contractPath cannot be null");
byte[] contractBytes = Common.fileToBytes(contractPath);
registerContract(id, name, contractBytes, properties);
}
/**
* Registers the specified contract for the certificate holder specified in {@code ClientConfig}.
* To execute the contract, you need to use the corresponding {@code executeContract} methods that
* match the type of the contract.
*
* @param id an ID for the contract
* @param name the binary name of the contract class
* @param contractBytes the byte array 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, @Nullable String 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.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().getCertVersion())
.setContractId(id)
.setContractBinaryName(name)
.setContractByteCode(ByteString.copyFrom(contractBytes));
if (properties != null) {
builder.setContractProperties(properties);
}
ContractRegistrationRequest request = signer.sign(builder).build();
registerToAuditor(request);
client.register(request);
}
/**
* 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.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().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 (and functions) with the specified argument. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(String contractId, JsonObject contractArgument) {
return executeContract(contractId, contractArgument, null, null);
}
/**
* Executes the specified contract (and functions) with the specified argument. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce, String contractId, JsonObject contractArgument) {
return executeContract(nonce, contractId, contractArgument, null, null);
}
/**
* Executes the specified contract (and functions) with the specified arguments. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* The contract argument can have a "_functions_" key to specify an array of function IDs to
* execute with the specified contract, but this feature is deprecated and will be removed in
* release 5.0.0.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
* @deprecated This method will be removed in release 5.0.0.
*/
@Deprecated
public ContractExecutionResult executeContract(
String contractId, JsonObject contractArgument, Optional functionArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument, functionArgument);
}
/**
* Executes the specified contract (and functions) with the specified arguments. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* The contract argument can have a "_functions_" key to specify an array of function IDs to
* execute with the specified contract, but this feature is deprecated and will be removed in
* release 5.0.0.
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
* @deprecated This method will be removed in release 5.0.0.
*/
@Deprecated
public ContractExecutionResult executeContract(
String nonce,
String contractId,
JsonObject contractArgument,
Optional functionArgument) {
List functionIds = Argument.getFunctionIds(contractArgument);
return executeContractInternal(
nonce,
contractId,
Argument.format(contractArgument, nonce).toString(),
functionIds,
functionArgument.map(Object::toString).orElse(null));
}
/**
* Executes the specified contract (and function) with the specified arguments. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String contractId,
JsonObject contractArgument,
@Nullable String functionId,
@Nullable JsonObject functionArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument, functionId, functionArgument);
}
/**
* Executes the specified contract (and functions) with the specified arguments. The contract and
* functions have to be based on JSONP, such as {@code JsonpBasedContract} and {@code
* JsonpBasedFunction}.
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce,
String contractId,
JsonObject contractArgument,
@Nullable String functionId,
@Nullable JsonObject functionArgument) {
return executeContractInternal(
nonce,
contractId,
Argument.format(contractArgument, nonce, Collections.singletonList(functionId)),
functionId != null ? Collections.singletonList(functionId) : Collections.emptyList(),
functionArgument != null ? functionArgument.toString() : null);
}
/**
* Executes the specified contract with the specified argument. The contract and functions have to
* be based on Jackson, such as {@code JacksonBasedContract} and {@code JacksonBasedFunction}.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(String contractId, JsonNode contractArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument, null, null);
}
/**
* Executes the specified contract with the specified argument. The contract has to be based on
* Jackson (i.e., {@code JacksonBasedContract}).
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce, String contractId, JsonNode contractArgument) {
return executeContract(nonce, contractId, contractArgument, null, null);
}
/**
* Executes the specified contract (and functions) with the specified arguments. The contract and
* functions have to be based on Jackson, such as {@code JacksonBasedContract} and {@code
* JacksonBasedFunction}.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String contractId,
JsonNode contractArgument,
@Nullable String functionId,
@Nullable JsonNode functionArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument, functionId, functionArgument);
}
/**
* Executes the specified contract (and functions) with the specified arguments. The contract and
* functions have to be based on Jackson, such as {@code JacksonBasedContract} and {@code
* JacksonBasedFunction}.
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce,
String contractId,
JsonNode contractArgument,
@Nullable String functionId,
@Nullable JsonNode functionArgument) {
return executeContractInternal(
nonce,
contractId,
Argument.format(contractArgument, nonce, Collections.singletonList(functionId)),
functionId != null ? Collections.singletonList(functionId) : Collections.emptyList(),
functionArgument != null ? jacksonSerDe.serialize(functionArgument) : null);
}
/**
* Executes the specified contract with the specified argument. The contract has to be based on
* String (i.e., {@code StringBasedContract}).
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(String contractId, String contractArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument);
}
/**
* Executes the specified contract with the specified argument. The contract has to be based on
* String (i.e., {@code StringBasedContract}).
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce, String contractId, String contractArgument) {
return executeContractInternal(
nonce,
contractId,
Argument.format(contractArgument, nonce, Collections.emptyList()),
Collections.emptyList(),
null);
}
/**
* Executes the specified contract (and function) with the specified arguments. The contract and
* functions have to be based on String, such as {@code StringBasedContract} and {@code
* StringBasedFunction}.
*
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String contractId,
String contractArgument,
@Nullable String functionId,
@Nullable String functionArgument) {
String nonce = UUID.randomUUID().toString();
return executeContract(nonce, contractId, contractArgument, functionId, functionArgument);
}
/**
* Executes the specified contract (and function) with the specified arguments. The contract and
* functions have to be based on String, such as {@code StringBasedContract} and {@code
* StringBasedFunction}.
*
* @param nonce a unique ID of the execution request
* @param contractId an ID of the contract
* @param contractArgument an argument of the contract
* @param functionId an ID of the function
* @param functionArgument an argument of the function
* @return {@link ContractExecutionResult}
* @throws ClientException if a request fails for some reason
*/
public ContractExecutionResult executeContract(
String nonce,
String contractId,
String contractArgument,
@Nullable String functionId,
@Nullable String functionArgument) {
return executeContractInternal(
nonce,
contractId,
Argument.format(contractArgument, nonce, Collections.singletonList(functionId)),
functionId != null ? Collections.singletonList(functionId) : Collections.emptyList(),
functionArgument);
}
private ContractExecutionResult executeContractInternal(
String nonce,
String contractId,
String contractArgument,
List functionIds,
@Nullable String functionArgument) {
checkClientMode(ClientMode.CLIENT);
checkArgument(contractId != null, "contractId cannot be null");
checkArgument(contractArgument != null, "contractArgument cannot be null");
ContractExecutionRequest.Builder builder =
ContractExecutionRequest.newBuilder()
.setNonce(nonce)
.setCertHolderId(config.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().getCertVersion())
.setContractId(contractId)
.setContractArgument(contractArgument);
if (!functionIds.isEmpty()) {
builder.setUseFunctionIds(true).addAllFunctionIds(functionIds);
}
if (functionArgument != null) {
builder.setFunctionArgument(functionArgument);
}
assert signer != null;
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.
*
* @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.
*
* @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()) {
return validateLedgerWithContractExecution(assetId, startAge, endAge);
} else {
LedgerValidationRequest.Builder builder =
LedgerValidationRequest.newBuilder()
.setCertHolderId(config.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().getCertVersion())
.setAssetId(assetId)
.setStartAge(startAge)
.setEndAge(endAge);
LedgerValidationRequest request = signer.sign(builder).build();
return client.validate(request);
}
}
/**
* 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()) {
throw new UnsupportedOperationException(
"validateLedger with Auditor 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);
}
return client.validate(request);
}
/**
* 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
* @deprecated This method will be removed in release 4.0.0.
*/
@Deprecated
public LedgersValidationResult validateLedgers(String assetId) {
checkClientMode(ClientMode.CLIENT);
checkArgument(assetId != null, "assetId cannot be null");
LedgersValidationRequest.Builder builder =
LedgersValidationRequest.newBuilder()
.setCertHolderId(config.getIdentityConfig().getCertHolderId())
.setCertVersion(config.getIdentityConfig().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
* @deprecated This method will be removed in release 4.0.0.
*/
@Deprecated
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);
}
/**
* It does nothing now. It's left here for backward compatibility.
*
* @deprecated This method will be removed in release 5.0.0.
*/
@Deprecated
@Override
public void close() {
// Does nothing. it's left here for backward compatibility
}
private void checkClientMode(ClientMode expected) {
checkArgument(config.getClientMode().equals(expected), "wrong mode specified.");
}
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.getContractResult().equals(auditorResponse.getContractResult())
|| 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 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.getLedgerProofs().get(0), result.getAuditorProofs().get(0));
}
@VisibleForTesting
AbstractLedgerClient getLedgerClient() {
return client;
}
@VisibleForTesting
AbstractAuditorClient getAuditorClient() {
return auditorClient;
}
@VisibleForTesting
RequestSigner getRequestSigner() {
return signer;
}
}