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

org.fisco.bcos.sdk.v3.client.ClientImpl Maven / Gradle / Ivy

/**
 * Copyright 2014-2020 [fisco-dev]
 *
 * 

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * *

http://www.apache.org/licenses/LICENSE-2.0 * *

Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.fisco.bcos.sdk.v3.client; import static org.fisco.bcos.sdk.v3.utils.ObjectMapperFactory.getObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import org.fisco.bcos.sdk.jni.BcosSDKJniObj; import org.fisco.bcos.sdk.jni.rpc.RpcJniObj; import org.fisco.bcos.sdk.v3.client.exceptions.ClientException; import org.fisco.bcos.sdk.v3.client.protocol.model.GroupNodeIniConfig; import org.fisco.bcos.sdk.v3.client.protocol.model.GroupNodeIniInfo; import org.fisco.bcos.sdk.v3.client.protocol.request.JsonRpcMethods; import org.fisco.bcos.sdk.v3.client.protocol.request.JsonRpcRequest; import org.fisco.bcos.sdk.v3.client.protocol.request.LogFilterRequest; import org.fisco.bcos.sdk.v3.client.protocol.request.Transaction; import org.fisco.bcos.sdk.v3.client.protocol.response.Abi; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosBlock; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosGroupInfo; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosGroupInfoList; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosGroupList; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosGroupNodeInfo; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosTransaction; import org.fisco.bcos.sdk.v3.client.protocol.response.BcosTransactionReceipt; import org.fisco.bcos.sdk.v3.client.protocol.response.BlockHash; import org.fisco.bcos.sdk.v3.client.protocol.response.BlockNumber; import org.fisco.bcos.sdk.v3.client.protocol.response.Call; import org.fisco.bcos.sdk.v3.client.protocol.response.Code; import org.fisco.bcos.sdk.v3.client.protocol.response.ConsensusStatus; import org.fisco.bcos.sdk.v3.client.protocol.response.GroupPeers; import org.fisco.bcos.sdk.v3.client.protocol.response.LogFilterResponse; import org.fisco.bcos.sdk.v3.client.protocol.response.LogWrapper; import org.fisco.bcos.sdk.v3.client.protocol.response.ObserverList; import org.fisco.bcos.sdk.v3.client.protocol.response.PbftView; import org.fisco.bcos.sdk.v3.client.protocol.response.Peers; import org.fisco.bcos.sdk.v3.client.protocol.response.PendingTxSize; import org.fisco.bcos.sdk.v3.client.protocol.response.SealerList; import org.fisco.bcos.sdk.v3.client.protocol.response.SyncStatus; import org.fisco.bcos.sdk.v3.client.protocol.response.SystemConfig; import org.fisco.bcos.sdk.v3.client.protocol.response.TotalTransactionCount; import org.fisco.bcos.sdk.v3.client.protocol.response.UninstallLogFilter; import org.fisco.bcos.sdk.v3.config.ConfigOption; import org.fisco.bcos.sdk.v3.contract.precompiled.sysconfig.SystemConfigFeature; import org.fisco.bcos.sdk.v3.contract.precompiled.sysconfig.SystemConfigService; import org.fisco.bcos.sdk.v3.crypto.CryptoSuite; import org.fisco.bcos.sdk.v3.model.CryptoType; import org.fisco.bcos.sdk.v3.model.EnumNodeVersion; import org.fisco.bcos.sdk.v3.model.JsonRpcResponse; import org.fisco.bcos.sdk.v3.model.Response; import org.fisco.bcos.sdk.v3.model.callback.RespCallback; import org.fisco.bcos.sdk.v3.model.callback.ResponseCallback; import org.fisco.bcos.sdk.v3.model.callback.TransactionCallback; import org.fisco.bcos.sdk.v3.utils.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ClientImpl implements Client { private static final Logger logger = LoggerFactory.getLogger(ClientImpl.class); private static final int BLOCK_LIMIT_RANGE = 500; // ------------basic group info -------------- private String groupID = ""; private String chainID; private Boolean wasm; private Boolean authCheck = false; private Boolean enableCommittee = false; private boolean serialExecute; private Boolean smCrypto; private String extraData = ""; // ------------basic group info -------------- // ------------ runtime info ----------------- private long blockNumber = 0; private String nodeToSendRequest = ""; private int negotiatedProtocol = 0; private final ConfigOption configOption; private BcosGroupInfo.GroupInfo groupInfo; private GroupNodeIniConfig groupNodeIniConfig; private CryptoSuite cryptoSuite; private RpcJniObj rpcJniObj; protected final ObjectMapper objectMapper = getObjectMapper(); protected void initGroupInfo() { this.groupInfo = getGroupInfo().getResult(); if (groupInfo == null) { logger.error("The group not exist, groupID: {}", groupID); throw new ClientException( "The group not exist, please check the groupID, groupID: " + this.groupID); } List nodeList = groupInfo.getNodeList(); if (nodeList == null || nodeList.isEmpty()) { logger.error( "There has no nodes in the group, groupID: {}, groupInfo: {}", groupID, groupInfo); throw new ClientException( "There has no nodes in the group, maybe the group has been removed, groupID: " + this.groupID); } BcosGroupNodeInfo.GroupNodeInfo groupNodeInfo = groupInfo.getNodeList().get(0); GroupNodeIniInfo nodeIniConfig = groupNodeInfo.getIniConfig(); long compatibilityVersion = groupNodeInfo.getProtocol().getCompatibilityVersion(); this.groupNodeIniConfig = GroupNodeIniConfig.newIniConfig(nodeIniConfig); this.chainID = groupNodeIniConfig.getChain().getChainID(); this.wasm = groupNodeIniConfig.getExecutor().isWasm(); this.serialExecute = groupNodeIniConfig.getExecutor().isSerialExecute(); this.authCheck = groupNodeIniConfig.getExecutor().isAuthCheck(); this.enableCommittee = groupNodeIniConfig.getExecutor().isAuthCheck(); if (EnumNodeVersion.valueFromCompatibilityVersion(compatibilityVersion) .compareTo(EnumNodeVersion.BCOS_3_3_0.toVersionObj()) >= 0) { this.enableCommittee = true; try { SystemConfig systemConfig = getSystemConfigByKey(SystemConfigService.AUTH_STATUS); int value = Integer.parseInt(systemConfig.getSystemConfig().getValue()); if (value != 0) { this.authCheck = true; } } catch (Exception ignored) { this.authCheck = false; } } this.smCrypto = groupNodeIniConfig.getChain().isSmCrypto(); this.blockNumber = this.getBlockNumber().getBlockNumber().longValue(); logger.info( "init group info in rpc, chainID: {}, smCrypto: {}, wasm: {}, authCheck:{}, serialExecute:{}, blockNumber: {}, GroupNodeIniConfig: {}", chainID, smCrypto, wasm, authCheck, serialExecute, blockNumber, groupNodeIniConfig); } protected ClientImpl(String groupID, ConfigOption configOption, long nativePointer) { this.groupID = groupID; this.configOption = configOption; this.rpcJniObj = RpcJniObj.build(nativePointer); // start rpc start(); // groupID is set, init group basic info, eg: chain_id, sm_crypto, is_wasm if (Objects.nonNull(groupID) && !groupID.isEmpty()) { initGroupInfo(); // init crypto suite if (smCrypto) { // init HSM crypto suite if (configOption.getCryptoMaterialConfig() != null && configOption.getCryptoMaterialConfig().getEnableHsm()) { this.cryptoSuite = new CryptoSuite(CryptoType.HSM_TYPE, configOption); } else { this.cryptoSuite = new CryptoSuite(CryptoType.SM_TYPE, configOption); } } else { this.cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE, configOption); } } // should be assigned after session connect this.negotiatedProtocol = BcosSDKJniObj.negotiatedProtocolInfo(nativePointer); logger.info( "ClientImpl constructor, groupID: {}, nativePointer: {}, smCrypto: {}, wasm: {}", groupID, nativePointer, smCrypto, isWASM()); } @Override public String getExtraData() { return extraData; } @Override public void setExtraData(String extraData) { this.extraData = extraData; } @Override public long getNativePointer() { return rpcJniObj.getNativePointer(); } @Override public ConfigOption getConfigOption() { return this.configOption; } @Override public String getGroup() { return this.groupID; } @Override public String getChainId() { return this.chainID; } public Boolean getSmCrypto() { return this.smCrypto; } @Override public CryptoSuite getCryptoSuite() { return this.cryptoSuite; } @Override public Integer getCryptoType() { return this.cryptoSuite.getCryptoTypeConfig(); } @Override public Boolean isWASM() { return this.wasm; } @Override public Boolean isAuthCheck() { return this.authCheck; } @Override public Boolean isEnableCommittee() { return this.enableCommittee; } @Override public Boolean isSerialExecute() { return this.serialExecute; } @Override public BcosTransactionReceipt sendTransaction(String signedTransactionData, boolean withProof) { return this.sendTransaction(nodeToSendRequest, signedTransactionData, withProof); } @Override public BcosTransactionReceipt sendTransaction( String node, String signedTransactionData, boolean withProof) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.SEND_TRANSACTION, Arrays.asList(this.groupID, node, signedTransactionData, withProof)), BcosTransactionReceipt.class); } @Override public void sendTransactionAsync( String signedTransactionData, boolean withProof, TransactionCallback callback) { this.sendTransactionAsync(nodeToSendRequest, signedTransactionData, withProof, callback); } @Override public void sendTransactionAsync( String node, String signedTransactionData, boolean withProof, TransactionCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.SEND_TRANSACTION, Arrays.asList(this.groupID, node, signedTransactionData, withProof)), BcosTransactionReceipt.class, new RespCallback() { @Override public void onResponse(BcosTransactionReceipt transactionReceiptWithProof) { callback.onResponse(transactionReceiptWithProof.getTransactionReceipt()); } @Override public void onError(Response errorResponse) { callback.onError( errorResponse.getErrorCode(), errorResponse.getErrorMessage()); } }); } @Override public Call call(Transaction transaction) { return this.call(nodeToSendRequest, transaction); } @Override public Call call(String node, Transaction transaction) { node = Objects.isNull(node) ? "" : node; if (logger.isTraceEnabled()) { logger.trace("client call, to:{}, data:{}", transaction.getTo(), transaction.getData()); } return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.CALL, Arrays.asList( this.groupID, node, Hex.trimPrefix(transaction.getTo()), Hex.toHexString(transaction.getData()))), Call.class); } @Override public Call call(Transaction transaction, String sign) { return call(nodeToSendRequest, transaction, sign); } @Override public Call call(String node, Transaction transaction, String sign) { node = Objects.isNull(node) ? "" : node; if (logger.isTraceEnabled()) { logger.trace( "client call, to:{}, data:{}, sign:{}", transaction.getTo(), transaction.getData(), sign); } return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.CALL, Arrays.asList( this.groupID, node, Hex.trimPrefix(transaction.getTo()), Hex.toHexString(transaction.getData()), sign)), Call.class); } @Override public void callAsync(Transaction transaction, RespCallback callback) { this.callAsync(nodeToSendRequest, transaction, callback); } @Override public void callAsync(String node, Transaction transaction, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.CALL, Arrays.asList( this.groupID, node, Hex.trimPrefix(transaction.getTo()), Hex.toHexString(transaction.getData()))), Call.class, callback); } @Override public void callAsync(Transaction transaction, String sign, RespCallback callback) { this.callAsync(nodeToSendRequest, transaction, sign, callback); } @Override public void callAsync( String node, Transaction transaction, String sign, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.CALL, Arrays.asList( this.groupID, node, Hex.trimPrefix(transaction.getTo()), Hex.toHexString(transaction.getData()), sign)), Call.class, callback); } @Override public BlockNumber getBlockNumber() { return this.getBlockNumber(nodeToSendRequest); } @Override public BlockNumber getBlockNumber(String node) { node = Objects.isNull(node) ? "" : node; // create request return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_NUMBER, Arrays.asList(this.groupID, node)), BlockNumber.class); } @Override public void getBlockNumberAsync(RespCallback callback) { this.getBlockNumberAsync(nodeToSendRequest, callback); } @Override public void getBlockNumberAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_NUMBER, Arrays.asList(this.groupID, node)), BlockNumber.class, callback); } @Override public Code getCode(String address) { return this.getCode(nodeToSendRequest, address); } @Override public Code getCode(String node, String address) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_CODE, Arrays.asList(this.groupID, node, Hex.trimPrefix(address))), Code.class); } @Override public void getCodeAsync(String address, RespCallback callback) { this.getCodeAsync(nodeToSendRequest, address, callback); } @Override public void getCodeAsync(String node, String address, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_CODE, Arrays.asList(this.groupID, node, Hex.trimPrefix(address))), Code.class, callback); } @Override public Abi getABI(String address) { return this.getABI(nodeToSendRequest, address); } @Override public Abi getABI(String node, String address) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_ABI, Arrays.asList(this.groupID, node, Hex.trimPrefix(address))), Abi.class); } @Override public void getABIAsync(String address, RespCallback callback) { this.getABIAsync(nodeToSendRequest, address, callback); } @Override public void getABIAsync(String node, String address, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_ABI, Arrays.asList(this.groupID, node, Hex.trimPrefix(address))), Abi.class, callback); } @Override public TotalTransactionCount getTotalTransactionCount() { return this.getTotalTransactionCount(nodeToSendRequest); } @Override public TotalTransactionCount getTotalTransactionCount(String node) { node = Objects.isNull(node) ? "" : node; // create request for getTotalTransactionCount return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TOTAL_TRANSACTION_COUNT, Arrays.asList(this.groupID, node)), TotalTransactionCount.class); } @Override public void getTotalTransactionCountAsync(RespCallback callback) { this.getTotalTransactionCountAsync(nodeToSendRequest, callback); } @Override public void getTotalTransactionCountAsync( String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TOTAL_TRANSACTION_COUNT, Arrays.asList(this.groupID, node)), TotalTransactionCount.class, callback); } @Override public BcosBlock getBlockByHash(String blockHash, boolean onlyHeader, boolean onlyTxHash) { return this.getBlockByHash(nodeToSendRequest, blockHash, onlyHeader, onlyTxHash); } @Override public BcosBlock getBlockByHash( String node, String blockHash, boolean onlyHeader, boolean onlyTxHash) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_BY_HASH, Arrays.asList(this.groupID, node, blockHash, onlyHeader, onlyTxHash)), BcosBlock.class); } @Override public void getBlockByHashAsync( String blockHash, boolean onlyHeader, boolean onlyTxHash, RespCallback callback) { this.getBlockByHashAsync(nodeToSendRequest, blockHash, onlyHeader, onlyTxHash, callback); } @Override public void getBlockByHashAsync( String node, String blockHash, boolean onlyHeader, boolean onlyTxHash, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_BY_HASH, Arrays.asList(this.groupID, node, blockHash, onlyHeader, onlyTxHash)), BcosBlock.class, callback); } @Override public BcosBlock getBlockByNumber( BigInteger blockNumber, boolean onlyHeader, boolean onlyTxHash) { return this.getBlockByNumber(nodeToSendRequest, blockNumber, onlyHeader, onlyTxHash); } @Override public BcosBlock getBlockByNumber( String node, BigInteger blockNumber, boolean onlyHeader, boolean onlyTxHash) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_BY_NUMBER, Arrays.asList(this.groupID, node, blockNumber, onlyHeader, onlyTxHash)), BcosBlock.class); } @Override public void getBlockByNumberAsync( BigInteger blockNumber, boolean onlyHeader, boolean onlyTxHash, RespCallback callback) { this.getBlockByNumberAsync( nodeToSendRequest, blockNumber, onlyHeader, onlyTxHash, callback); } @Override public void getBlockByNumberAsync( String node, BigInteger blockNumber, boolean onlyHeader, boolean onlyTxHash, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCK_BY_NUMBER, Arrays.asList(this.groupID, node, blockNumber, onlyHeader, onlyTxHash)), BcosBlock.class, callback); } @Override public BlockHash getBlockHashByNumber(BigInteger blockNumber) { return this.getBlockHashByNumber(nodeToSendRequest, blockNumber); } @Override public BlockHash getBlockHashByNumber(String node, BigInteger blockNumber) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCKHASH_BY_NUMBER, Arrays.asList(this.groupID, node, blockNumber)), BlockHash.class); } @Override public void getBlockHashByNumberAsync( BigInteger blockNumber, RespCallback callback) { this.getBlockHashByNumberAsync(nodeToSendRequest, blockNumber, callback); } @Override public void getBlockHashByNumberAsync( String node, BigInteger blockNumber, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_BLOCKHASH_BY_NUMBER, Arrays.asList(this.groupID, node, blockNumber)), BlockHash.class, callback); } @Override public BcosTransaction getTransaction(String transactionHash, Boolean withProof) { return this.getTransaction(nodeToSendRequest, transactionHash, withProof); } @Override public BcosTransaction getTransaction(String node, String transactionHash, Boolean withProof) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TRANSACTION_BY_HASH, Arrays.asList(this.groupID, node, transactionHash, withProof)), BcosTransaction.class); } @Override public void getTransactionAsync( String transactionHash, Boolean withProof, RespCallback callback) { this.getTransactionAsync(nodeToSendRequest, transactionHash, withProof, callback); } @Override public void getTransactionAsync( String node, String transactionHash, Boolean withProof, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TRANSACTION_BY_HASH, Arrays.asList(this.groupID, node, transactionHash)), BcosTransaction.class, callback); } @Override public BcosTransactionReceipt getTransactionReceipt(String transactionHash, Boolean withProof) { return this.getTransactionReceipt(nodeToSendRequest, transactionHash, withProof); } @Override public BcosTransactionReceipt getTransactionReceipt( String node, String transactionHash, Boolean withProof) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TRANSACTION_RECEIPT, Arrays.asList(this.groupID, node, transactionHash, withProof)), BcosTransactionReceipt.class); } @Override public void getTransactionReceiptAsync( String transactionHash, Boolean withProof, RespCallback callback) { this.getTransactionReceiptAsync(nodeToSendRequest, transactionHash, withProof, callback); } @Override public void getTransactionReceiptAsync( String node, String transactionHash, Boolean withProof, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_TRANSACTION_RECEIPT, Arrays.asList(this.groupID, node, transactionHash, withProof)), BcosTransactionReceipt.class, callback); } @Override public PendingTxSize getPendingTxSize() { return this.getPendingTxSize(nodeToSendRequest); } @Override public PendingTxSize getPendingTxSize(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_PENDING_TX_SIZE, Arrays.asList(this.groupID, node)), PendingTxSize.class); } @Override public void getPendingTxSizeAsync(RespCallback callback) { this.getPendingTxSizeAsync(nodeToSendRequest, callback); } @Override public void getPendingTxSizeAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_PENDING_TX_SIZE, Arrays.asList(this.groupID, node)), PendingTxSize.class, callback); } @Override public BigInteger getBlockLimit() { BigInteger blockLimit = BigInteger.valueOf(this.rpcJniObj.getBlockLimit(this.groupID)); if (logger.isDebugEnabled()) { logger.debug("getBlockLimit, group: {}, blockLimit: {}", groupID, blockLimit); } if (blockLimit.compareTo(BigInteger.ZERO) <= 0) { blockLimit = BigInteger.valueOf(blockNumber).add(BigInteger.valueOf(BLOCK_LIMIT_RANGE)); } return blockLimit; } @Override public GroupPeers getGroupPeers() { return this.callRemoteMethod( this.groupID, nodeToSendRequest, new JsonRpcRequest<>( JsonRpcMethods.GET_GROUP_PEERS, Arrays.asList(this.groupID, "")), GroupPeers.class); } @Override public void getGroupPeersAsync(RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, nodeToSendRequest, new JsonRpcRequest<>( JsonRpcMethods.GET_GROUP_PEERS, Arrays.asList(this.groupID, "")), GroupPeers.class, callback); } @Override public Peers getPeers() { return this.callRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_PEERS, Collections.emptyList()), Peers.class); } @Override public void getPeersAsync(RespCallback callback) { this.asyncCallRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_PEERS, Collections.emptyList()), Peers.class, callback); } @Override public ObserverList getObserverList() { return this.getObserverList(nodeToSendRequest); } @Override public ObserverList getObserverList(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_OBSERVER_LIST, Arrays.asList(this.groupID, node)), ObserverList.class); } @Override public void getObserverList(RespCallback callback) { this.getObserverList(nodeToSendRequest, callback); } @Override public void getObserverList(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_OBSERVER_LIST, Arrays.asList(this.groupID, node)), ObserverList.class, callback); } @Override public SealerList getSealerList() { return this.getSealerList(nodeToSendRequest); } @Override public SealerList getSealerList(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SEALER_LIST, Arrays.asList(this.groupID, node)), SealerList.class); } @Override public void getSealerListAsync(RespCallback callback) { this.getSealerListAsync(nodeToSendRequest, callback); } @Override public void getSealerListAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SEALER_LIST, Arrays.asList(this.groupID, node)), SealerList.class, callback); } @Override public SealerList getNodeListByType(String type) { return getNodeListByType(nodeToSendRequest, type); } @Override public SealerList getNodeListByType(String node, String type) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_NODE_LIST_BY_TYPE, Arrays.asList(this.groupID, node, type)), SealerList.class); } @Override public void getNodeListByTypeAsync(String type, RespCallback callback) { this.getNodeListByTypeAsync(nodeToSendRequest, type, callback); } @Override public void getNodeListByTypeAsync( String node, String type, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_NODE_LIST_BY_TYPE, Arrays.asList(this.groupID, node, type)), SealerList.class, callback); } @Override public PbftView getPbftView() { return this.getPbftView(""); } @Override public void getPbftViewAsync(RespCallback callback) { this.getPbftViewAsync(nodeToSendRequest, callback); } @Override public PbftView getPbftView(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_PBFT_VIEW, Arrays.asList(this.groupID, node)), PbftView.class); } @Override public void getPbftViewAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_PBFT_VIEW, Arrays.asList(this.groupID, node)), PbftView.class, callback); } @Override public SystemConfig getSystemConfigByKey(String key) { SystemConfig config = this.getSystemConfigByKey(nodeToSendRequest, key); if (key.equals(SystemConfigService.TX_GAS_PRICE)) { String gasPrice = config.getSystemConfig().getValue(); BigInteger gasPriceValue = new BigInteger(Hex.trimPrefix(gasPrice), 16); config.getSystemConfig().setValue(gasPriceValue.toString()); } return config; } @Override public SystemConfig getSystemConfigByKey(String node, String key) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SYSTEM_CONFIG_BY_KEY, Arrays.asList(this.groupID, node, key)), SystemConfig.class); } @Override public Map> getSystemConfigList() { CompletableFuture>> future = new CompletableFuture<>(); this.getSystemConfigListAsync( new RespCallback>>() { @Override public void onResponse(Map> configMap) { future.complete(configMap); } @Override public void onError(Response errorResponse) { future.completeExceptionally( new ClientException( "getSystemConfigList failed, error: " + errorResponse.getErrorMessage())); } }); try { return future.get(configOption.getNetworkConfig().getTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { logger.warn("getSystemConfigList failed, error: {}", e.getMessage(), e); throw new ClientException("getSystemConfigList failed, error: " + e.getMessage(), e); } } @Override public void getSystemConfigByKeyAsync(String key, RespCallback callback) { this.getSystemConfigByKeyAsync(nodeToSendRequest, key, callback); } @Override public void getSystemConfigByKeyAsync( String node, String key, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SYSTEM_CONFIG_BY_KEY, Arrays.asList(this.groupID, node, key)), SystemConfig.class, callback); } @Override public void getSupportSysConfigKeysAsync(RespCallback> callback) { this.getGroupInfoListAsync( new RespCallback() { @Override public void onResponse(BcosGroupInfoList bcosGroupInfoList) { Optional group = bcosGroupInfoList.getResult().stream() .filter(gInfo -> gInfo.getGroupID().equals(getGroup())) .findFirst(); Set keys = new TreeSet<>(); keys.addAll( Arrays.stream(SystemConfigFeature.Features.values()) .map(SystemConfigFeature.Features::toString) .collect(Collectors.toList())); keys.addAll(SystemConfigService.getConfigKeys()); if (group.isPresent() && !group.get().getNodeList().isEmpty()) { group.get() .getNodeList() .forEach( groupNodeInfo -> { keys.addAll(groupNodeInfo.getFeatureKeys()); keys.addAll(groupNodeInfo.getSupportConfigs()); }); } callback.onResponse(keys); } @Override public void onError(Response errorResponse) { callback.onError(errorResponse); } }); } @Override public void getSystemConfigListAsync( RespCallback>> callback) { this.getSupportSysConfigKeysAsync( new RespCallback>() { @Override public void onResponse(Set keys) { Map> configMap = new ConcurrentSkipListMap<>(); if (keys.isEmpty()) { callback.onResponse(configMap); return; } keys.forEach( key -> getSystemConfigByKeyAsync( key, new RespCallback() { @Override public void onResponse( SystemConfig systemConfig) { configMap.put( key, Optional.ofNullable(systemConfig)); if (configMap.size() == keys.size()) { callback.onResponse(configMap); } } @Override public void onError(Response errorResponse) { // maybe not exist configMap.put(key, Optional.empty()); if (configMap.size() == keys.size()) { callback.onResponse(configMap); } } })); } @Override public void onError(Response errorResponse) { callback.onError(errorResponse); } }); } @Override public SyncStatus getSyncStatus(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SYNC_STATUS, Arrays.asList(this.groupID, node)), SyncStatus.class); } @Override public SyncStatus getSyncStatus() { return getSyncStatus(nodeToSendRequest); } @Override public void getSyncStatusAsync(RespCallback callback) { this.getSyncStatusAsync(nodeToSendRequest, callback); } @Override public void getSyncStatusAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_SYNC_STATUS, Arrays.asList(this.groupID, node)), SyncStatus.class, callback); } @Override public void getConsensusStatusAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_CONSENSUS_STATUS, Arrays.asList(this.groupID, node)), ConsensusStatus.class, callback); } @Override public void getConsensusStatusAsync(RespCallback callback) { this.getConsensusStatusAsync(nodeToSendRequest, callback); } @Override public ConsensusStatus getConsensusStatus(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_CONSENSUS_STATUS, Arrays.asList(this.groupID, node)), ConsensusStatus.class); } @Override public ConsensusStatus getConsensusStatus() { return getConsensusStatus(nodeToSendRequest); } @Override public BcosGroupList getGroupList() { return this.callRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_GROUP_LIST, Collections.emptyList()), BcosGroupList.class); } @Override public void getGroupListAsync(RespCallback callback) { this.asyncCallRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_GROUP_LIST, Collections.emptyList()), BcosGroupList.class, callback); } @Override public BcosGroupInfo getGroupInfo() { try { CompletableFuture future = new CompletableFuture<>(); this.rpcJniObj.getGroupInfo( groupID, resp -> { Response response = new Response(); response.setErrorCode(resp.getErrorCode()); response.setErrorMessage(resp.getErrorMessage()); response.setContent(resp.getData()); if (logger.isDebugEnabled()) { logger.debug("getGroupInfo onResponse: {}", response); } future.complete(response); }); Response response = future.get(configOption.getNetworkConfig().getTimeout(), TimeUnit.MILLISECONDS); return ClientImpl.parseResponseIntoJsonRpcResponse( JsonRpcMethods.GET_GROUP_INFO, response, BcosGroupInfo.class); } catch (ClientException e) { logger.error("e: ", e); throw new ClientException( e.getErrorCode(), e.getErrorMessage(), "getGroupInfo failed for decode the message exception, error message:" + e.getMessage()); } catch (InterruptedException | ExecutionException e) { logger.error("e: ", e); throw new ClientException( "getGroupInfo failed for decode the message exception, error message:" + e.getMessage(), e); } catch (TimeoutException e) { logger.error("e: ", e); throw new ClientException( "getGroupInfo failed for get group info timeout, error message:" + e.getMessage(), e); } } @Override public void getGroupInfoAsync(RespCallback callback) { rpcJniObj.getGroupInfo( this.groupID, resp -> { Response response = new Response(); response.setErrorCode(resp.getErrorCode()); response.setErrorMessage(resp.getErrorMessage()); response.setContent(resp.getData()); ResponseCallback responseCallback = createResponseCallback( JsonRpcMethods.GET_GROUP_INFO, BcosGroupInfo.class, callback); if (logger.isDebugEnabled()) { logger.debug("getGroupInfo onResponse: {}", response); } responseCallback.onResponse(response); }); } @Override public BcosGroupInfoList getGroupInfoList() { return this.callRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_GROUP_INFO_LIST, Collections.emptyList()), BcosGroupInfoList.class); } @Override public void getGroupInfoListAsync(RespCallback callback) { this.asyncCallRemoteMethod( "", nodeToSendRequest, new JsonRpcRequest<>(JsonRpcMethods.GET_GROUP_INFO_LIST, Collections.emptyList()), BcosGroupInfoList.class, callback); } @Override public BcosGroupNodeInfo getGroupNodeInfo(String node) { node = Objects.isNull(node) ? "" : node; return this.callRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_GROUP_NODE_INFO, Arrays.asList(groupID, node)), BcosGroupNodeInfo.class); } @Override public void getGroupNodeInfoAsync(String node, RespCallback callback) { node = Objects.isNull(node) ? "" : node; this.asyncCallRemoteMethod( this.groupID, node, new JsonRpcRequest<>( JsonRpcMethods.GET_GROUP_NODE_INFO, Arrays.asList(groupID, node)), BcosGroupNodeInfo.class, callback); } @Override @Deprecated public EnumNodeVersion getChainVersion() { List nodeList = getGroupInfo().getResult().getNodeList(); if (nodeList == null || nodeList.isEmpty()) { throw new IllegalStateException("Empty node list in group info."); } long compatibilityVersion = nodeList.get(0).getProtocol().getCompatibilityVersion(); return EnumNodeVersion.valueOf((int) compatibilityVersion); } @Override public EnumNodeVersion.Version getChainCompatibilityVersion() { List nodeList = getGroupInfo().getResult().getNodeList(); if (nodeList == null || nodeList.isEmpty()) { throw new IllegalStateException("Empty node list in group info."); } long compatibilityVersion = nodeList.get(0).getProtocol().getCompatibilityVersion(); return EnumNodeVersion.valueFromCompatibilityVersion(compatibilityVersion); } @Override public void getChainCompatibilityVersionAsync( RespCallback versionRespCallback) { getGroupInfoAsync( new RespCallback() { @Override public void onResponse(BcosGroupInfo bcosGroupInfo) { List nodeList = bcosGroupInfo.getResult().getNodeList(); if (nodeList == null || nodeList.isEmpty()) { versionRespCallback.onError( new Response(-1, "Empty node list in group info.")); return; } long compatibilityVersion = nodeList.get(0).getProtocol().getCompatibilityVersion(); versionRespCallback.onResponse( EnumNodeVersion.valueFromCompatibilityVersion( compatibilityVersion)); } @Override public void onError(Response errorResponse) { versionRespCallback.onError(errorResponse); } }); } @Override public void setNodeToSendRequest(String nodeToSendRequest) { if (null == nodeToSendRequest || nodeToSendRequest.isEmpty()) { this.nodeToSendRequest = ""; return; } List nodeList = getGroupInfo().getResult().getNodeList(); if (nodeList.stream().anyMatch(node -> node.getName().equals(nodeToSendRequest))) { this.nodeToSendRequest = nodeToSendRequest; } } @Override public boolean isSupportTransactionV1() { int protocol = getNegotiatedProtocol(); return (protocol >> 16) >= 2; } @Override public boolean isSupportTransactionV2() { return isSupportTransactionV1() && getGroupInfoList().getResult().stream() .filter(group -> Objects.equals(groupInfo.getGroupID(), groupID)) .allMatch( info -> info.getNodeList().stream() .allMatch( node -> EnumNodeVersion.getClassVersion( node.getIniConfig() .getBinaryInfo() .getVersion()) .toCompatibilityVersion() >= EnumNodeVersion .BCOS_3_7_0 .getVersion())); } @Override public String getNodeToSendRequest() { return this.nodeToSendRequest; } /** * Get the protocol version after SDK and Blockchain node negotiated. This method returns int * with max and min version bits combined, which is (max|min). Max protocol version is in first * 16 bit, and min protocol version in the second 16 bit. * * @return (max | min) bits combined. */ @Override public int getNegotiatedProtocol() { return negotiatedProtocol; } @Override public LogFilterResponse newFilter(LogFilterRequest params) { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.NEW_FILTER, Arrays.asList(this.groupID, params)), LogFilterResponse.class); } @Override public void newFilterAsync(LogFilterRequest params, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>(JsonRpcMethods.NEW_FILTER, Arrays.asList(groupID, params)), LogFilterResponse.class, callback); } @Override public LogFilterResponse newBlockFilter() { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>(JsonRpcMethods.NEW_BLOCK_FILTER, Arrays.asList(this.groupID)), LogFilterResponse.class); } @Override public void newBlockFilterAsync(RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>(JsonRpcMethods.NEW_BLOCK_FILTER, Arrays.asList(this.groupID)), LogFilterResponse.class, callback); } @Override public LogFilterResponse newPendingTransactionFilter() { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.NEW_PENDING_TX_FILTER, Arrays.asList(this.groupID)), LogFilterResponse.class); } @Override public void newPendingTransactionFilterAsync(RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.NEW_PENDING_TX_FILTER, Arrays.asList(this.groupID)), LogFilterResponse.class, callback); } @Override public LogWrapper getFilterChanges(LogFilterResponse filter) { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.GET_FILTER_CHANGES, Arrays.asList(this.groupID, filter.getResult())), LogWrapper.class); } @Override public void getFilterChangesAsync(LogFilterResponse filter, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.GET_FILTER_CHANGES, Arrays.asList(this.groupID, filter.getResult())), LogWrapper.class, callback); } @Override public UninstallLogFilter uninstallFilter(LogFilterResponse filter) { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.UNINSTALL_FILTER, Arrays.asList(this.groupID, filter.getResult())), UninstallLogFilter.class); } @Override public void uninstallFilterAsync( LogFilterResponse filter, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.UNINSTALL_FILTER, Arrays.asList(this.groupID, filter.getResult())), UninstallLogFilter.class, callback); } @Override public LogWrapper getLogs(LogFilterRequest params) { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>(JsonRpcMethods.GET_LOGS, Arrays.asList(this.groupID, params)), LogWrapper.class); } @Override public void getLogsAsync(LogFilterRequest params, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>(JsonRpcMethods.GET_LOGS, Arrays.asList(this.groupID, params)), LogWrapper.class, callback); } @Override public LogWrapper getFilterLogs(LogFilterResponse filter) { return this.callRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.GET_FILTER_LOGS, Arrays.asList(this.groupID, filter.getResult())), LogWrapper.class); } @Override public void getFilterLogsAsync(LogFilterResponse filter, RespCallback callback) { this.asyncCallRemoteMethod( this.groupID, "", new JsonRpcRequest<>( JsonRpcMethods.GET_FILTER_LOGS, Arrays.asList(this.groupID, filter.getResult())), LogWrapper.class, callback); } @Override public void start() { if (rpcJniObj != null) { rpcJniObj.start(); } } @Override public void stop() { if (rpcJniObj != null) { rpcJniObj.stop(); } } @Override public void destroy() { if (rpcJniObj != null) { BcosSDKJniObj.destroy(rpcJniObj.getNativePointer()); rpcJniObj = null; } if (cryptoSuite != null) { cryptoSuite.destroy(); cryptoSuite = null; } } public static > ResponseCallback createResponseCallback( String method, Class responseType, RespCallback callback) { return new ResponseCallback() { @Override public void onResponse(Response response) { try { // decode the transaction T jsonRpcResponse = ClientImpl.parseResponseIntoJsonRpcResponse( method, response, responseType); callback.onResponse(jsonRpcResponse); } catch (ClientException e) { response.setErrorCode(e.getErrorCode()); response.setErrorMessage(e.getErrorMessage()); callback.onError(response); } } }; } public > T callRemoteMethod( String groupID, String node, JsonRpcRequest request, Class responseType) { try { CompletableFuture future = new CompletableFuture<>(); String data = this.objectMapper.writeValueAsString(request); this.rpcJniObj.genericMethod( groupID, node, data, resp -> { Response response = new Response(); response.setErrorCode(resp.getErrorCode()); response.setErrorMessage(resp.getErrorMessage()); response.setContent(resp.getData()); if (logger.isTraceEnabled()) { logger.trace( " callRemoteMethod ===>>> request: {}, response: {}", request, response); } future.complete(response); }); Response response = future.get(); return ClientImpl.parseResponseIntoJsonRpcResponse( request.getMethod(), response, responseType); } catch (ClientException e) { logger.info("callRemoteMethod ClientException, raw request:{} ", request, e); throw new ClientException( e.getErrorCode(), e.getErrorMessage(), "callRemoteMethod failed for decode the message exception, error message:" + e.getMessage(), e); } catch (JsonProcessingException | InterruptedException | ExecutionException e) { logger.error("callRemoteMethod exception, raw request:{} ", request, e); throw new ClientException( "callRemoteMethod failed for decode the message exception, error message:" + e.getMessage(), e); } } public > void asyncCallRemoteMethod( String groupID, String node, JsonRpcRequest request, Class responseType, RespCallback callback) { try { this.rpcJniObj.genericMethod( groupID, node, this.objectMapper.writeValueAsString(request), resp -> { Response response = new Response(); response.setErrorCode(resp.getErrorCode()); response.setErrorMessage(resp.getErrorMessage()); response.setContent(resp.getData()); if (logger.isTraceEnabled()) { logger.trace( " ===>>> asyncCallRemoteMethod, group: {}, node: {}, request: {}, response: {}", groupID, node, request, response); } ResponseCallback responseCallback = createResponseCallback(request.getMethod(), responseType, callback); responseCallback.onResponse(response); }); } catch (JsonProcessingException e) { logger.error("e: ", e); } } public static > T parseResponseIntoJsonRpcResponse( String method, Response response, Class responseType) throws ClientException { try { if (response.getErrorCode() == 0) { // parse the response into JsonRPCResponse T jsonRpcResponse = getObjectMapper().readValue(response.getContent(), responseType); // error code inside json rpc response if (jsonRpcResponse.hasError()) { logger.info( "parseResponseIntoJsonRpcResponse failed for non-empty error message, method: {}, msg: {}, code: {}, rawRsp: {}", method, jsonRpcResponse.getError().getMessage(), jsonRpcResponse.getError().getCode(), response.getContentString()); throw new ClientException( jsonRpcResponse.getError().getCode(), jsonRpcResponse.getError().getMessage(), "msg: " + jsonRpcResponse.getError().getMessage()); } return jsonRpcResponse; } else { logger.info( "parseResponseIntoJsonRpcResponse failed, method: {}, msg: {}, code: {}, rawRsp: {}", method, response.getErrorMessage(), response.getErrorCode(), response.getContent()); throw new ClientException( response.getErrorCode(), response.getErrorMessage(), "get response failed, code: " + response.getErrorCode() + ", msg: " + response.getErrorMessage()); } } catch (ClientException e) { logger.info( "parseResponseIntoJsonRpcResponse failed for decode the message exception, response: {}, errorMessage: {}", response, e.getMessage(), e); throw e; } catch (Exception e) { logger.info( "parseResponseIntoJsonRpcResponse failed for decode the message exception, response: {}, errorMessage: {}", response, e.getMessage(), e); throw new ClientException(e.getMessage(), e); } } }