Add support for private log subscriptions (Pub-Sub API) (#858)

* Created priv_subscribe and priv_unsubscribe methods

Signed-off-by: Lucas Saldanha <lucas.saldanha@consensys.net>
pull/904/head
Lucas Saldanha 5 years ago committed by GitHub
parent f2244ee531
commit 06ca344bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.md
  2. 62
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  3. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  4. 23
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/EnclavePublicKeyProvider.java
  5. 22
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java
  6. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketRequestHandler.java
  7. 43
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java
  8. 64
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java
  9. 66
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java
  10. 127
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java
  11. 21
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/WebSocketMethodsFactory.java
  12. 17
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/SubscriptionBuilder.java
  13. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/SubscriptionManager.java
  14. 43
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/logs/LogsSubscriptionService.java
  15. 35
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/logs/PrivateLogsSubscription.java
  16. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/InvalidSubscriptionRequestException.java
  17. 58
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/PrivateSubscribeRequest.java
  18. 52
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/PrivateUnsubscribeRequest.java
  19. 49
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java
  20. 22
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/response/SubscriptionResponse.java
  21. 18
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/response/SubscriptionResponseResult.java
  22. 29
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketRequestHandlerTest.java
  23. 144
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java
  24. 150
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java
  25. 18
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/SubscriptionBuilderTest.java
  26. 5
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/SubscriptionManagerSendMessageTest.java
  27. 69
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/logs/LogsSubscriptionServiceTest.java
  28. 53
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java
  29. 2
      ethereum/referencetests/src/test/resources

@ -11,6 +11,12 @@ What this means for you:
permissions on the directory allow other users and groups to r/w. Ideally this should be set to
`besu:besu` and `orion:orion` as the owners.
## 1.4.6
### Additions and Improvements
- Implemented WebSocket logs subscription for private contracts (`priv_subscribe`/`priv_unsubscribe`) [#762]
## 1.4.5
### Additions and Improvements

@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethodsFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketRequestHandler;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketService;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods.PrivateWebSocketMethodsFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods.WebSocketMethodsFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.blockheaders.NewBlockHeadersSubscriptionService;
@ -50,6 +51,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.pending.
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.pending.PendingTransactionSubscriptionService;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.syncing.SyncingSubscriptionService;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.MiningParameters;
@ -84,6 +86,7 @@ import org.hyperledger.besu.ethereum.permissioning.node.NodePermissioningControl
import org.hyperledger.besu.ethereum.permissioning.node.PeerPermissionsAdapter;
import org.hyperledger.besu.ethereum.stratum.StratumServer;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.metrics.prometheus.MetricsService;
@ -523,7 +526,11 @@ public class RunnerBuilder {
final SubscriptionManager subscriptionManager =
createSubscriptionManager(vertx, transactionPool);
createLogsSubscriptionService(context.getBlockchain(), subscriptionManager);
createLogsSubscriptionService(
context.getBlockchain(),
context.getWorldStateArchive(),
subscriptionManager,
privacyParameters);
createNewBlockHeadersSubscriptionService(
context.getBlockchain(), blockchainQueries, subscriptionManager);
@ -533,7 +540,14 @@ public class RunnerBuilder {
webSocketService =
Optional.of(
createWebsocketService(
vertx, webSocketConfiguration, subscriptionManager, webSocketsJsonRpcMethods));
vertx,
webSocketConfiguration,
subscriptionManager,
webSocketsJsonRpcMethods,
privacyParameters,
protocolSchedule,
blockchainQueries,
transactionPool));
}
Optional<MetricsService> metricsService = Optional.empty();
@ -698,11 +712,31 @@ public class RunnerBuilder {
}
private void createLogsSubscriptionService(
final Blockchain blockchain, final SubscriptionManager subscriptionManager) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final SubscriptionManager subscriptionManager,
final PrivacyParameters privacyParameters) {
Optional<PrivacyQueries> privacyQueries = Optional.empty();
if (privacyParameters.isEnabled()) {
final BlockchainQueries blockchainQueries =
new BlockchainQueries(blockchain, worldStateArchive);
privacyQueries =
Optional.of(
new PrivacyQueries(
blockchainQueries, privacyParameters.getPrivateWorldStateReader()));
}
final LogsSubscriptionService logsSubscriptionService =
new LogsSubscriptionService(subscriptionManager);
new LogsSubscriptionService(subscriptionManager, privacyQueries);
// monitoring public logs
blockchain.observeLogs(logsSubscriptionService);
// monitoring private logs
if (privacyParameters.isEnabled()) {
blockchain.observeBlockAdded(logsSubscriptionService::checkPrivateLogs);
}
}
private void createSyncingSubscriptionService(
@ -724,9 +758,27 @@ public class RunnerBuilder {
final Vertx vertx,
final WebSocketConfiguration configuration,
final SubscriptionManager subscriptionManager,
final Map<String, JsonRpcMethod> jsonRpcMethods) {
final Map<String, JsonRpcMethod> jsonRpcMethods,
final PrivacyParameters privacyParameters,
final ProtocolSchedule<?> protocolSchedule,
final BlockchainQueries blockchainQueries,
final TransactionPool transactionPool) {
final WebSocketMethodsFactory websocketMethodsFactory =
new WebSocketMethodsFactory(subscriptionManager, jsonRpcMethods);
if (privacyParameters.isEnabled()) {
final PrivateWebSocketMethodsFactory privateWebSocketMethodsFactory =
new PrivateWebSocketMethodsFactory(
privacyParameters,
subscriptionManager,
protocolSchedule,
blockchainQueries,
transactionPool);
privateWebSocketMethodsFactory.methods().forEach(websocketMethodsFactory::addMethods);
}
final WebSocketRequestHandler websocketRequestHandler =
new WebSocketRequestHandler(vertx, websocketMethodsFactory.methods());

@ -52,6 +52,8 @@ public enum RpcMethod {
PRIV_UNINSTALL_FILTER("priv_uninstallFilter"),
PRIV_GET_FILTER_CHANGES("priv_getFilterChanges"),
PRIV_GET_FILTER_LOGS("priv_getFilterLogs"),
PRIV_SUBSCRIBE("priv_subscribe"),
PRIV_UNSUBSCRIBE("priv_unsubscribe"),
PRIVX_FIND_PRIVACY_GROUP("privx_findOnChainPrivacyGroup"),
EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"),
ETH_ACCOUNTS("eth_accounts"),

@ -14,11 +14,34 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyUserUtil.enclavePublicKey;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import java.util.Optional;
import io.vertx.ext.auth.User;
@FunctionalInterface
public interface EnclavePublicKeyProvider {
String getEnclaveKey(Optional<User> user);
static EnclavePublicKeyProvider build(final PrivacyParameters privacyParameters) {
return privacyParameters.isMultiTenancyEnabled()
? multiTenancyEnclavePublicKeyProvider()
: defaultEnclavePublicKeyProvider(privacyParameters);
}
private static EnclavePublicKeyProvider multiTenancyEnclavePublicKeyProvider() {
return user ->
enclavePublicKey(user)
.orElseThrow(
() -> new IllegalStateException("Request does not contain an authorization token"));
}
private static EnclavePublicKeyProvider defaultEnclavePublicKeyProvider(
final PrivacyParameters privacyParameters) {
return user -> privacyParameters.getEnclavePublicKey();
}
}

@ -14,8 +14,6 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.methods;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyUserUtil.enclavePublicKey;
import org.hyperledger.besu.ethereum.api.jsonrpc.LatestNonceProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.DisabledPrivacyRpcMethod;
@ -94,7 +92,8 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
final PrivateMarkerTransactionFactory markerTransactionFactory =
createPrivateMarkerTransactionFactory(
privacyParameters, blockchainQueries, transactionPool.getPendingTransactions());
final EnclavePublicKeyProvider enclavePublicProvider = createEnclavePublicKeyProvider();
final EnclavePublicKeyProvider enclavePublicProvider =
EnclavePublicKeyProvider.build(privacyParameters);
final PrivacyController privacyController = createPrivacyController(markerTransactionFactory);
return create(privacyController, enclavePublicProvider).entrySet().stream()
.collect(
@ -123,23 +122,6 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
return new RandomSigningPrivateMarkerTransactionFactory(privateContractAddress);
}
private EnclavePublicKeyProvider createEnclavePublicKeyProvider() {
return privacyParameters.isMultiTenancyEnabled()
? multiTenancyEnclavePublicKeyProvider()
: defaultEnclavePublicKeyProvider();
}
private EnclavePublicKeyProvider multiTenancyEnclavePublicKeyProvider() {
return user ->
enclavePublicKey(user)
.orElseThrow(
() -> new IllegalStateException("Request does not contain an authorization token"));
}
private EnclavePublicKeyProvider defaultEnclavePublicKeyProvider() {
return user -> privacyParameters.getEnclavePublicKey();
}
private PrivacyController createPrivacyController(
final PrivateMarkerTransactionFactory markerTransactionFactory) {
final Optional<BigInteger> chainId = protocolSchedule.getChainId();

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationService;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationUtils;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
@ -83,6 +84,9 @@ public class WebSocketRequestHandler {
future.complete(
new JsonRpcUnauthorizedResponse(request.getId(), JsonRpcError.UNAUTHORIZED));
}
} catch (final InvalidJsonRpcParameters e) {
LOG.debug("Invalid Params", e);
future.complete(new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS));
} catch (final Exception e) {
LOG.error(JsonRpcError.INTERNAL_ERROR.getMessage(), e);
future.complete(new JsonRpcErrorResponse(request.getId(), JsonRpcError.INTERNAL_ERROR));

@ -0,0 +1,43 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
abstract class AbstractPrivateSubscriptionMethod extends AbstractSubscriptionMethod {
private final PrivacyController privacyController;
private final EnclavePublicKeyProvider enclavePublicKeyProvider;
AbstractPrivateSubscriptionMethod(
final SubscriptionManager subscriptionManager,
final SubscriptionRequestMapper mapper,
final PrivacyController privacyController,
final EnclavePublicKeyProvider enclavePublicKeyProvider) {
super(subscriptionManager, mapper);
this.privacyController = privacyController;
this.enclavePublicKeyProvider = enclavePublicKeyProvider;
}
void checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(
final JsonRpcRequestContext request, final String privacyGroupId) {
final String enclavePublicKey = enclavePublicKeyProvider.getEnclaveKey(request.getUser());
privacyController.verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey);
}
}

@ -0,0 +1,64 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.InvalidSubscriptionRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateSubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
public class PrivSubscribe extends AbstractPrivateSubscriptionMethod {
public PrivSubscribe(
final SubscriptionManager subscriptionManager,
final SubscriptionRequestMapper mapper,
final PrivacyController privacyController,
final EnclavePublicKeyProvider enclavePublicKeyProvider) {
super(subscriptionManager, mapper, privacyController, enclavePublicKeyProvider);
}
@Override
public String getName() {
return RpcMethod.PRIV_SUBSCRIBE.getMethodName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
try {
final PrivateSubscribeRequest subscribeRequest =
getMapper().mapPrivateSubscribeRequest(requestContext);
checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(
requestContext, subscribeRequest.getPrivacyGroupId());
final Long subscriptionId = subscriptionManager().subscribe(subscribeRequest);
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(), Quantity.create(subscriptionId));
} catch (final InvalidSubscriptionRequestException isEx) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_REQUEST);
}
}
}

@ -0,0 +1,66 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionNotFoundException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.InvalidSubscriptionRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateUnsubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
public class PrivUnsubscribe extends AbstractPrivateSubscriptionMethod {
public PrivUnsubscribe(
final SubscriptionManager subscriptionManager,
final SubscriptionRequestMapper mapper,
final PrivacyController privacyController,
final EnclavePublicKeyProvider enclavePublicKeyProvider) {
super(subscriptionManager, mapper, privacyController, enclavePublicKeyProvider);
}
@Override
public String getName() {
return RpcMethod.PRIV_UNSUBSCRIBE.getMethodName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
try {
final PrivateUnsubscribeRequest unsubscribeRequest =
getMapper().mapPrivateUnsubscribeRequest(requestContext);
checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(
requestContext, unsubscribeRequest.getPrivacyGroupId());
final boolean unsubscribed = subscriptionManager().unsubscribe(unsubscribeRequest);
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), unsubscribed);
} catch (final InvalidSubscriptionRequestException isEx) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_REQUEST);
} catch (final SubscriptionNotFoundException snfEx) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.SUBSCRIPTION_NOT_FOUND);
}
}
}

@ -0,0 +1,127 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.LatestNonceProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.privacy.ChainHeadPrivateNonceProvider;
import org.hyperledger.besu.ethereum.privacy.DefaultPrivacyController;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateNonceProvider;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionSimulator;
import org.hyperledger.besu.ethereum.privacy.markertransaction.FixedKeySigningPrivateMarkerTransactionFactory;
import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory;
import org.hyperledger.besu.ethereum.privacy.markertransaction.RandomSigningPrivateMarkerTransactionFactory;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
public class PrivateWebSocketMethodsFactory {
private final PrivacyParameters privacyParameters;
private final SubscriptionManager subscriptionManager;
private final ProtocolSchedule<?> protocolSchedule;
private final BlockchainQueries blockchainQueries;
private final TransactionPool transactionPool;
public PrivateWebSocketMethodsFactory(
final PrivacyParameters privacyParameters,
final SubscriptionManager subscriptionManager,
final ProtocolSchedule<?> protocolSchedule,
final BlockchainQueries blockchainQueries,
final TransactionPool transactionPool) {
this.privacyParameters = privacyParameters;
this.subscriptionManager = subscriptionManager;
this.protocolSchedule = protocolSchedule;
this.blockchainQueries = blockchainQueries;
this.transactionPool = transactionPool;
}
public Collection<JsonRpcMethod> methods() {
final SubscriptionRequestMapper subscriptionRequestMapper = new SubscriptionRequestMapper();
final EnclavePublicKeyProvider enclavePublicKeyProvider =
EnclavePublicKeyProvider.build(privacyParameters);
final PrivacyController privacyController = createPrivacyController();
return Set.of(
new PrivSubscribe(
subscriptionManager,
subscriptionRequestMapper,
privacyController,
enclavePublicKeyProvider),
new PrivUnsubscribe(
subscriptionManager,
subscriptionRequestMapper,
privacyController,
enclavePublicKeyProvider));
}
private PrivateMarkerTransactionFactory createPrivateMarkerTransactionFactory() {
final Address privateContractAddress =
Address.privacyPrecompiled(privacyParameters.getPrivacyAddress());
if (privacyParameters.getSigningKeyPair().isPresent()) {
return new FixedKeySigningPrivateMarkerTransactionFactory(
privateContractAddress,
new LatestNonceProvider(blockchainQueries, transactionPool.getPendingTransactions()),
privacyParameters.getSigningKeyPair().get());
}
return new RandomSigningPrivateMarkerTransactionFactory(privateContractAddress);
}
private PrivacyController createPrivacyController() {
final Optional<BigInteger> chainId = protocolSchedule.getChainId();
final DefaultPrivacyController defaultPrivacyController =
new DefaultPrivacyController(
blockchainQueries.getBlockchain(),
privacyParameters,
chainId,
createPrivateMarkerTransactionFactory(),
createPrivateTransactionSimulator(),
createPrivateNonceProvider(),
privacyParameters.getPrivateWorldStateReader());
return privacyParameters.isMultiTenancyEnabled()
? new MultiTenancyPrivacyController(
defaultPrivacyController, chainId, privacyParameters.getEnclave())
: defaultPrivacyController;
}
private PrivateTransactionSimulator createPrivateTransactionSimulator() {
return new PrivateTransactionSimulator(
blockchainQueries.getBlockchain(),
blockchainQueries.getWorldStateArchive(),
protocolSchedule,
privacyParameters);
}
private PrivateNonceProvider createPrivateNonceProvider() {
return new ChainHeadPrivateNonceProvider(
blockchainQueries.getBlockchain(),
privacyParameters.getPrivateStateRootResolver(),
privacyParameters.getPrivateWorldStateArchive());
}
}

@ -23,31 +23,28 @@ import java.util.Map;
public class WebSocketMethodsFactory {
private final SubscriptionManager subscriptionManager;
private final Map<String, JsonRpcMethod> jsonRpcMethods;
private final Map<String, JsonRpcMethod> methods = new HashMap<>();
public WebSocketMethodsFactory(
final SubscriptionManager subscriptionManager,
final Map<String, JsonRpcMethod> jsonRpcMethods) {
this.subscriptionManager = subscriptionManager;
this.jsonRpcMethods = jsonRpcMethods;
this.methods.putAll(jsonRpcMethods);
buildWebsocketMethods(subscriptionManager);
}
public Map<String, JsonRpcMethod> methods() {
final Map<String, JsonRpcMethod> websocketMethods = new HashMap<>();
websocketMethods.putAll(jsonRpcMethods);
private void buildWebsocketMethods(final SubscriptionManager subscriptionManager) {
addMethods(
websocketMethods,
new EthSubscribe(subscriptionManager, new SubscriptionRequestMapper()),
new EthUnsubscribe(subscriptionManager, new SubscriptionRequestMapper()));
return websocketMethods;
}
public Map<String, JsonRpcMethod> addMethods(
final Map<String, JsonRpcMethod> methods, final JsonRpcMethod... rpcMethods) {
public Map<String, JsonRpcMethod> methods() {
return methods;
}
public void addMethods(final JsonRpcMethod... rpcMethods) {
for (final JsonRpcMethod rpcMethod : rpcMethods) {
methods.put(rpcMethod.getName(), rpcMethod);
}
return methods;
}
}

@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.blockheaders.NewBlockHeadersSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs.LogsSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs.PrivateLogsSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateSubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionType;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.syncing.SyncingSubscription;
@ -35,7 +37,7 @@ public class SubscriptionBuilder {
}
case LOGS:
{
return new LogsSubscription(subscriptionId, connectionId, request.getFilterParameter());
return logsSubscription(subscriptionId, connectionId, request);
}
case SYNCING:
{
@ -48,6 +50,19 @@ public class SubscriptionBuilder {
}
}
private Subscription logsSubscription(
final long subscriptionId, final String connectionId, final SubscribeRequest request) {
if (request instanceof PrivateSubscribeRequest) {
return new PrivateLogsSubscription(
subscriptionId,
connectionId,
request.getFilterParameter(),
((PrivateSubscribeRequest) request).getPrivacyGroupId());
} else {
return new LogsSubscription(subscriptionId, connectionId, request.getFilterParameter());
}
}
@SuppressWarnings("unchecked")
public <T> Function<Subscription, T> mapToSubscriptionClass(final Class<T> clazz) {
return subscription -> {

@ -134,10 +134,10 @@ public class SubscriptionManager extends AbstractVerticle {
}
public void sendMessage(final Long subscriptionId, final JsonRpcResult msg) {
final SubscriptionResponse response = new SubscriptionResponse(subscriptionId, msg);
final Subscription subscription = subscriptions.get(subscriptionId);
if (subscription != null) {
final SubscriptionResponse response = new SubscriptionResponse(subscription, msg);
vertx.eventBus().send(subscription.getConnectionId(), Json.encode(response));
}
}

@ -18,17 +18,25 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParam
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionType;
import org.hyperledger.besu.ethereum.api.query.LogsQuery;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import java.util.Optional;
import java.util.function.Consumer;
public class LogsSubscriptionService implements Consumer<LogWithMetadata> {
private final SubscriptionManager subscriptionManager;
private final Optional<PrivacyQueries> privacyQueries;
public LogsSubscriptionService(final SubscriptionManager subscriptionManager) {
public LogsSubscriptionService(
final SubscriptionManager subscriptionManager,
final Optional<PrivacyQueries> privacyQueries) {
this.subscriptionManager = subscriptionManager;
this.privacyQueries = privacyQueries;
}
@Override
@ -46,9 +54,34 @@ public class LogsSubscriptionService implements Consumer<LogWithMetadata> {
&& filterParameter.getToBlock().getNumber().orElse(Long.MAX_VALUE) >= blockNumber
&& filterParameter.getLogsQuery().matches(logWithMetadata);
})
.forEach(
logsSubscription ->
subscriptionManager.sendMessage(
logsSubscription.getSubscriptionId(), new LogResult(logWithMetadata)));
.forEach(logsSubscription -> sendLogToSubscription(logWithMetadata, logsSubscription));
}
public void checkPrivateLogs(final BlockAddedEvent event) {
privacyQueries.ifPresent(
pq ->
subscriptionManager.subscriptionsOfType(SubscriptionType.LOGS, LogsSubscription.class)
.stream()
.filter(PrivateLogsSubscription.class::isInstance)
.map(PrivateLogsSubscription.class::cast)
.forEach(queryPrivateEventForSubscription(pq, event)));
}
private Consumer<PrivateLogsSubscription> queryPrivateEventForSubscription(
final PrivacyQueries privacyQueries, final BlockAddedEvent event) {
return subscription -> {
final String privacyGroupId = subscription.getPrivacyGroupId();
final LogsQuery logsQuery = subscription.getFilterParameter().getLogsQuery();
privacyQueries
.matchingLogs(privacyGroupId, event.getBlock().getHash(), logsQuery)
.forEach(logWithMetadata -> sendLogToSubscription(logWithMetadata, subscription));
};
}
private void sendLogToSubscription(
final LogWithMetadata logWithMetadata, final LogsSubscription subscription) {
subscriptionManager.sendMessage(
subscription.getSubscriptionId(), new LogResult(logWithMetadata));
}
}

@ -0,0 +1,35 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter;
public class PrivateLogsSubscription extends LogsSubscription {
private final String privacyGroupId;
public PrivateLogsSubscription(
final Long subscriptionId,
final String connectionId,
final FilterParameter filterParameter,
final String privacyGroupId) {
super(subscriptionId, connectionId, filterParameter);
this.privacyGroupId = privacyGroupId;
}
public String getPrivacyGroupId() {
return privacyGroupId;
}
}

@ -20,6 +20,10 @@ public class InvalidSubscriptionRequestException extends RuntimeException {
super();
}
public InvalidSubscriptionRequestException(final String message) {
super(message);
}
public InvalidSubscriptionRequestException(final String message, final Throwable cause) {
super(message, cause);
}

@ -0,0 +1,58 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter;
import java.util.Objects;
public class PrivateSubscribeRequest extends SubscribeRequest {
private final String privacyGroupId;
public PrivateSubscribeRequest(
final SubscriptionType subscriptionType,
final FilterParameter filterParameter,
final Boolean includeTransaction,
final String connectionId,
final String privacyGroupId) {
super(subscriptionType, filterParameter, includeTransaction, connectionId);
this.privacyGroupId = privacyGroupId;
}
public String getPrivacyGroupId() {
return privacyGroupId;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
PrivateSubscribeRequest that = (PrivateSubscribeRequest) o;
return privacyGroupId.equals(that.privacyGroupId);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), privacyGroupId);
}
}

@ -0,0 +1,52 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request;
import java.util.Objects;
public class PrivateUnsubscribeRequest extends UnsubscribeRequest {
private final String privacyGroupId;
public PrivateUnsubscribeRequest(
final Long subscriptionId, final String connectionId, final String privacyGroupId) {
super(subscriptionId, connectionId);
this.privacyGroupId = privacyGroupId;
}
public String getPrivacyGroupId() {
return privacyGroupId;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
PrivateUnsubscribeRequest that = (PrivateUnsubscribeRequest) o;
return privacyGroupId.equals(that.privacyGroupId);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), privacyGroupId);
}
}

@ -83,11 +83,60 @@ public class SubscriptionRequestMapper {
final long subscriptionId =
webSocketRpcRequestBody.getRequiredParameter(0, UnsignedLongParameter.class).getValue();
return new UnsubscribeRequest(subscriptionId, webSocketRpcRequestBody.getConnectionId());
} catch (final Exception e) {
throw new InvalidSubscriptionRequestException("Error parsing unsubscribe request", e);
}
}
public PrivateSubscribeRequest mapPrivateSubscribeRequest(
final JsonRpcRequestContext jsonRpcRequestContext)
throws InvalidSubscriptionRequestException {
try {
final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext);
final String privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class);
final SubscriptionType subscriptionType =
webSocketRpcRequestBody.getRequiredParameter(1, SubscriptionType.class);
switch (subscriptionType) {
case LOGS:
{
final FilterParameter filterParameter =
jsonRpcRequestContext.getRequiredParameter(2, FilterParameter.class);
return new PrivateSubscribeRequest(
SubscriptionType.LOGS,
filterParameter,
null,
webSocketRpcRequestBody.getConnectionId(),
privacyGroupId);
}
default:
throw new InvalidSubscriptionRequestException(
"Invalid subscribe request. Invalid private subscription type.");
}
} catch (final InvalidSubscriptionRequestException e) {
throw e;
} catch (final Exception e) {
throw new InvalidSubscriptionRequestException("Error parsing subscribe request", e);
}
}
public PrivateUnsubscribeRequest mapPrivateUnsubscribeRequest(
final JsonRpcRequestContext jsonRpcRequestContext)
throws InvalidSubscriptionRequestException {
try {
final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext);
final String privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class);
final long subscriptionId =
webSocketRpcRequestBody.getRequiredParameter(1, UnsignedLongParameter.class).getValue();
return new PrivateUnsubscribeRequest(
subscriptionId, webSocketRpcRequestBody.getConnectionId(), privacyGroupId);
} catch (final Exception e) {
throw new InvalidSubscriptionRequestException("Error parsing unsubscribe request", e);
}
}
private WebSocketRpcRequest validateRequest(final JsonRpcRequestContext jsonRpcRequestContext) {
if (jsonRpcRequestContext.getRequest() instanceof WebSocketRpcRequest) {
return (WebSocketRpcRequest) jsonRpcRequestContext.getRequest();

@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.respons
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.JsonRpcResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.Subscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs.PrivateLogsSubscription;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@ -24,12 +26,24 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
public class SubscriptionResponse {
private static final String JSON_RPC_VERSION = "2.0";
private static final String METHOD_NAME = "eth_subscription";
private static final String ETH_SUBSCRIPTION_METHOD = "eth_subscription";
private static final String PRIV_SUBSCRIPTION_METHOD = "priv_subscription";
private final String methodName;
private final SubscriptionResponseResult params;
public SubscriptionResponse(final long subscriptionId, final JsonRpcResult result) {
this.params = new SubscriptionResponseResult(Quantity.create(subscriptionId), result);
public SubscriptionResponse(final Subscription subscription, final JsonRpcResult result) {
if (subscription instanceof PrivateLogsSubscription) {
final String privacyGroupId = ((PrivateLogsSubscription) subscription).getPrivacyGroupId();
this.methodName = PRIV_SUBSCRIPTION_METHOD;
this.params =
new SubscriptionResponseResult(
Quantity.create(subscription.getSubscriptionId()), result, privacyGroupId);
} else {
this.methodName = ETH_SUBSCRIPTION_METHOD;
this.params =
new SubscriptionResponseResult(Quantity.create(subscription.getSubscriptionId()), result);
}
}
@JsonGetter("jsonrpc")
@ -39,7 +53,7 @@ public class SubscriptionResponse {
@JsonGetter("method")
public String getMethod() {
return METHOD_NAME;
return methodName;
}
@JsonGetter("params")

@ -17,17 +17,28 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.respons
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.JsonRpcResult;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"subscription", "result"})
@JsonPropertyOrder({"subscription", "privacyGroupId", "result"})
public class SubscriptionResponseResult {
private final String subscription;
private final JsonRpcResult result;
@JsonInclude(Include.NON_NULL)
private final String privacyGroupId;
SubscriptionResponseResult(final String subscription, final JsonRpcResult result) {
this(subscription, result, null);
}
SubscriptionResponseResult(
final String subscription, final JsonRpcResult result, final String privacyGroupId) {
this.subscription = subscription;
this.result = result;
this.privacyGroupId = privacyGroupId;
}
@JsonGetter("subscription")
@ -39,4 +50,9 @@ public class SubscriptionResponseResult {
public JsonRpcResult getResult() {
return result;
}
@JsonGetter("privacyGroupId")
public String getPrivacyGroupId() {
return privacyGroupId;
}
}

@ -21,6 +21,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
@ -167,6 +168,34 @@ public class WebSocketRequestHandlerTest {
async.awaitSuccess(WebSocketRequestHandlerTest.VERTX_AWAIT_TIMEOUT_MILLIS);
}
@Test
public void onInvalidJsonRpcParametersExceptionProcessingRequestShouldRespondInvalidParams(
final TestContext context) {
final Async async = context.async();
final JsonObject requestJson = new JsonObject().put("id", 1).put("method", "eth_x");
final JsonRpcRequestContext expectedRequest =
new JsonRpcRequestContext(requestJson.mapTo(WebSocketRpcRequest.class));
when(jsonRpcMethodMock.response(eq(expectedRequest)))
.thenThrow(new InvalidJsonRpcParameters(""));
final JsonRpcErrorResponse expectedResponse =
new JsonRpcErrorResponse(1, JsonRpcError.INVALID_PARAMS);
final String websocketId = UUID.randomUUID().toString();
vertx
.eventBus()
.consumer(websocketId)
.handler(
msg -> {
context.assertEquals(Json.encode(expectedResponse), msg.body());
async.complete();
})
.completionHandler(v -> handler.handle(websocketId, requestJson.toString()));
async.awaitSuccess(WebSocketRequestHandlerTest.VERTX_AWAIT_TIMEOUT_MILLIS);
}
@Test
public void onExceptionProcessingRequestShouldRespondInternalError(final TestContext context) {
final Async async = context.async();

@ -0,0 +1,144 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.InvalidSubscriptionRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateSubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionType;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import io.vertx.core.json.Json;
import io.vertx.ext.auth.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PrivSubscribeTest {
private final String ENCLAVE_KEY = "enclave_key";
private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=";
@Mock private SubscriptionManager subscriptionManagerMock;
@Mock private SubscriptionRequestMapper mapperMock;
@Mock private PrivacyController privacyController;
@Mock private EnclavePublicKeyProvider enclavePublicKeyProvider;
private PrivSubscribe privSubscribe;
@Before
public void before() {
privSubscribe =
new PrivSubscribe(
subscriptionManagerMock, mapperMock, privacyController, enclavePublicKeyProvider);
}
@Test
public void expectedMethodName() {
assertThat(privSubscribe.getName()).isEqualTo("priv_subscribe");
}
@Test
public void responseContainsSubscriptionId() {
final WebSocketRpcRequest webSocketRequest = createWebSocketRpcRequest();
final JsonRpcRequestContext jsonRpcrequestContext = new JsonRpcRequestContext(webSocketRequest);
final PrivateSubscribeRequest subscribeRequest =
new PrivateSubscribeRequest(
SubscriptionType.LOGS,
null,
null,
webSocketRequest.getConnectionId(),
PRIVACY_GROUP_ID);
when(mapperMock.mapPrivateSubscribeRequest(eq(jsonRpcrequestContext)))
.thenReturn(subscribeRequest);
when(subscriptionManagerMock.subscribe(eq(subscribeRequest))).thenReturn(1L);
final JsonRpcSuccessResponse expectedResponse =
new JsonRpcSuccessResponse(
jsonRpcrequestContext.getRequest().getId(), Quantity.create((1L)));
assertThat(privSubscribe.response(jsonRpcrequestContext)).isEqualTo(expectedResponse);
}
@Test
public void invalidSubscribeRequestRespondsInvalidRequestResponse() {
final WebSocketRpcRequest webSocketRequest = createWebSocketRpcRequest();
final JsonRpcRequestContext jsonRpcrequestContext = new JsonRpcRequestContext(webSocketRequest);
when(mapperMock.mapPrivateSubscribeRequest(any()))
.thenThrow(new InvalidSubscriptionRequestException());
final JsonRpcErrorResponse expectedResponse =
new JsonRpcErrorResponse(
jsonRpcrequestContext.getRequest().getId(), JsonRpcError.INVALID_REQUEST);
assertThat(privSubscribe.response(jsonRpcrequestContext)).isEqualTo(expectedResponse);
}
@Test
public void multiTenancyCheckFailure() {
final User user = mock(User.class);
final WebSocketRpcRequest webSocketRequest = createWebSocketRpcRequest();
final JsonRpcRequestContext jsonRpcrequestContext =
new JsonRpcRequestContext(webSocketRequest, user);
final PrivateSubscribeRequest subscribeRequest =
new PrivateSubscribeRequest(
SubscriptionType.LOGS,
null,
null,
webSocketRequest.getConnectionId(),
PRIVACY_GROUP_ID);
when(mapperMock.mapPrivateSubscribeRequest(any())).thenReturn(subscribeRequest);
when(enclavePublicKeyProvider.getEnclaveKey(any())).thenReturn(ENCLAVE_KEY);
doThrow(new MultiTenancyValidationException("msg"))
.when(privacyController)
.verifyPrivacyGroupContainsEnclavePublicKey(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY));
assertThatThrownBy(() -> privSubscribe.response(jsonRpcrequestContext))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessageContaining("msg");
}
private WebSocketRpcRequest createWebSocketRpcRequest() {
return Json.decodeValue(
"{\"id\": 1, \"method\": \"priv_subscribe\", \"params\": [\""
+ PRIVACY_GROUP_ID
+ "\", \"logs\"], \"connectionId\": \"1\"}",
WebSocketRpcRequest.class);
}
}

@ -0,0 +1,150 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionNotFoundException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.InvalidSubscriptionRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateUnsubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionRequestMapper;
import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import io.vertx.core.json.Json;
import io.vertx.ext.auth.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PrivUnsubscribeTest {
private final String ENCLAVE_KEY = "enclave_key";
private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=";
private final String CONNECTION_ID = "test-connection-id";
@Mock private SubscriptionManager subscriptionManagerMock;
@Mock private SubscriptionRequestMapper mapperMock;
@Mock private PrivacyController privacyController;
@Mock private EnclavePublicKeyProvider enclavePublicKeyProvider;
private PrivUnsubscribe privUnsubscribe;
@Before
public void before() {
privUnsubscribe =
new PrivUnsubscribe(
subscriptionManagerMock, mapperMock, privacyController, enclavePublicKeyProvider);
}
@Test
public void expectedMethodName() {
assertThat(privUnsubscribe.getName()).isEqualTo("priv_unsubscribe");
}
@Test
public void responseContainsUnsubscribeStatus() {
final JsonRpcRequestContext request = createPrivUnsubscribeRequest();
final PrivateUnsubscribeRequest unsubscribeRequest =
new PrivateUnsubscribeRequest(1L, CONNECTION_ID, PRIVACY_GROUP_ID);
when(mapperMock.mapPrivateUnsubscribeRequest(eq(request))).thenReturn(unsubscribeRequest);
when(subscriptionManagerMock.unsubscribe(eq(unsubscribeRequest))).thenReturn(true);
final JsonRpcSuccessResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), true);
assertThat(privUnsubscribe.response(request)).isEqualTo(expectedResponse);
}
@Test
public void invalidUnsubscribeRequestReturnsInvalidRequestResponse() {
final JsonRpcRequestContext request = createPrivUnsubscribeRequest();
when(mapperMock.mapPrivateUnsubscribeRequest(any()))
.thenThrow(new InvalidSubscriptionRequestException());
final JsonRpcErrorResponse expectedResponse =
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_REQUEST);
assertThat(privUnsubscribe.response(request)).isEqualTo(expectedResponse);
}
@Test
public void whenSubscriptionNotFoundReturnError() {
final JsonRpcRequestContext request = createPrivUnsubscribeRequest();
when(mapperMock.mapPrivateUnsubscribeRequest(any()))
.thenReturn(mock(PrivateUnsubscribeRequest.class));
when(subscriptionManagerMock.unsubscribe(any()))
.thenThrow(new SubscriptionNotFoundException(1L));
final JsonRpcErrorResponse expectedResponse =
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.SUBSCRIPTION_NOT_FOUND);
assertThat(privUnsubscribe.response(request)).isEqualTo(expectedResponse);
}
@Test
public void multiTenancyCheckFailure() {
final User user = mock(User.class);
final JsonRpcRequestContext jsonRpcrequestContext = createPrivUnsubscribeRequestWithUser(user);
final PrivateUnsubscribeRequest unsubscribeRequest =
new PrivateUnsubscribeRequest(0L, CONNECTION_ID, PRIVACY_GROUP_ID);
when(mapperMock.mapPrivateUnsubscribeRequest(any())).thenReturn(unsubscribeRequest);
when(enclavePublicKeyProvider.getEnclaveKey(any())).thenReturn(ENCLAVE_KEY);
doThrow(new MultiTenancyValidationException("msg"))
.when(privacyController)
.verifyPrivacyGroupContainsEnclavePublicKey(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY));
assertThatThrownBy(() -> privUnsubscribe.response(jsonRpcrequestContext))
.isInstanceOf(MultiTenancyValidationException.class)
.hasMessageContaining("msg");
}
private JsonRpcRequestContext createPrivUnsubscribeRequest() {
return new JsonRpcRequestContext(
Json.decodeValue(
"{\"id\": 1, \"method\": \"priv_unsubscribe\", \"params\": [\""
+ PRIVACY_GROUP_ID
+ "\", \"0x0\"]}",
JsonRpcRequest.class));
}
private JsonRpcRequestContext createPrivUnsubscribeRequestWithUser(final User user) {
return new JsonRpcRequestContext(
Json.decodeValue(
"{\"id\": 1, \"method\": \"priv_unsubscribe\", \"params\": [\""
+ PRIVACY_GROUP_ID
+ "\", \"0x0\"]}",
JsonRpcRequest.class),
user);
}
}

@ -21,6 +21,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParame
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.blockheaders.NewBlockHeadersSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs.LogsSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs.PrivateLogsSubscription;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.PrivateSubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscribeRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request.SubscriptionType;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.syncing.SyncingSubscription;
@ -54,6 +56,22 @@ public class SubscriptionBuilderTest {
assertThat(builtSubscription).isEqualToComparingFieldByField(expectedSubscription);
}
@Test
public void shouldBuildPrivateLogsSubscriptionWhenSubscribeRequestTypeIsPrivateLogs() {
final String privacyGroupId = "ZDmkMK7CyxA1F1rktItzKFTfRwApg7aWzsTtm2IOZ5Y=";
final FilterParameter filterParameter = filterParameter();
final PrivateSubscribeRequest subscribeRequest =
new PrivateSubscribeRequest(
SubscriptionType.LOGS, filterParameter, null, CONNECTION_ID, privacyGroupId);
final PrivateLogsSubscription expectedSubscription =
new PrivateLogsSubscription(1L, CONNECTION_ID, filterParameter, privacyGroupId);
final Subscription builtSubscription =
subscriptionBuilder.build(1L, CONNECTION_ID, subscribeRequest);
assertThat(builtSubscription).isEqualToComparingFieldByField(expectedSubscription);
}
@Test
public void shouldBuildNewBlockHeadsSubscriptionWhenSubscribeRequestTypeIsNewBlockHeads() {
final SubscribeRequest subscribeRequest =

@ -57,7 +57,10 @@ public class SubscriptionManagerSendMessageTest {
new SubscribeRequest(SubscriptionType.SYNCING, null, null, connectionId);
final JsonRpcResult expectedResult = mock(JsonRpcResult.class);
final SubscriptionResponse expectedResponse = new SubscriptionResponse(1L, expectedResult);
final Subscription subscription =
new Subscription(1L, connectionId, SubscriptionType.SYNCING, false);
final SubscriptionResponse expectedResponse =
new SubscriptionResponse(subscription, expectedResult);
final Long subscriptionId = subscriptionManager.subscribe(subscribeRequest);

@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParam
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
@ -33,9 +34,11 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockWithReceipts;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.InMemoryStorageProvider;
import org.hyperledger.besu.ethereum.core.Log;
import org.hyperledger.besu.ethereum.core.LogTopic;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
@ -43,6 +46,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -69,10 +73,14 @@ public class LogsSubscriptionServiceTest {
@Mock private SubscriptionManager subscriptionManager;
@Mock private PrivacyQueries privacyQueries;
@Before
public void before() {
logsSubscriptionService = new LogsSubscriptionService(subscriptionManager);
logsSubscriptionService =
new LogsSubscriptionService(subscriptionManager, Optional.of(privacyQueries));
blockchain.observeLogs(logsSubscriptionService);
blockchain.observeBlockAdded(logsSubscriptionService::checkPrivateLogs);
}
@Test
@ -283,6 +291,38 @@ public class LogsSubscriptionServiceTest {
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture());
}
@Test
public void whenExistsPrivateLogsSubscriptionPrivacyQueriesIsCalled() {
final String privacyGroupId = "privacy_group_id";
final Address address = Address.fromHexString("0x0");
final PrivateLogsSubscription subscription = createPrivateSubscription(privacyGroupId, address);
registerSubscriptions(subscription);
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2);
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts());
verify(privacyQueries)
.matchingLogs(
eq(subscription.getPrivacyGroupId()),
eq(blockWithReceipts.getHash()),
eq(subscription.getFilterParameter().getLogsQuery()));
}
@Test
public void whenPrivateLogsSubscriptionMatchesLogNotificationIsSent() {
final String privacyGroupId = "privacy_group_id";
final Address address = Address.fromHexString("0x0");
final PrivateLogsSubscription subscription = createPrivateSubscription(privacyGroupId, address);
registerSubscriptions(subscription);
when(privacyQueries.matchingLogs(any(), any(), any())).thenReturn(List.of(logWithMetadata()));
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2);
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts());
verify(subscriptionManager, times(1)).sendMessage(eq(subscription.getSubscriptionId()), any());
}
private void assertLogResultMatches(
final LogResult result,
final Block block,
@ -347,6 +387,20 @@ public class LogsSubscriptionServiceTest {
return new BlockWithReceipts(block, receipts);
}
private PrivateLogsSubscription createPrivateSubscription(
final String privacyGroupId, final Address address) {
return new PrivateLogsSubscription(
nextSubscriptionId.incrementAndGet(),
"conn",
new FilterParameter(
BlockParameter.LATEST,
BlockParameter.LATEST,
Arrays.asList(address),
Collections.emptyList(),
null),
privacyGroupId);
}
private LogsSubscription createSubscription(final Address address) {
return createSubscription(Arrays.asList(address), Collections.emptyList());
}
@ -369,4 +423,17 @@ public class LogsSubscriptionServiceTest {
when(subscriptionManager.subscriptionsOfType(any(), any()))
.thenReturn(Lists.newArrayList(subscriptions));
}
private LogWithMetadata logWithMetadata() {
return new LogWithMetadata(
0,
100L,
Hash.ZERO,
Hash.ZERO,
0,
Address.fromHexString("0x0"),
Bytes.EMPTY,
Lists.newArrayList(),
false);
}
}

@ -374,6 +374,59 @@ public class SubscriptionRequestMapperTest {
mapper.mapSubscribeRequest(new JsonRpcRequestContext(jsonRpcRequest));
}
@Test
public void mapRequestToPrivateLogsSubscription() {
final JsonRpcRequest jsonRpcRequest =
parseWebSocketRpcRequest(
"{\"id\": 1, \"method\": \"priv_subscribe\", \"params\": [\"B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=\", \"logs\", {\"address\": \"0x8320fe7702b96808f7bbc0d4a888ed1468216cfd\"}]}");
final PrivateSubscribeRequest expectedSubscribeRequest =
new PrivateSubscribeRequest(
SubscriptionType.LOGS,
new FilterParameter(
BlockParameter.LATEST,
BlockParameter.LATEST,
singletonList(Address.fromHexString("0x8320fe7702b96808f7bbc0d4a888ed1468216cfd")),
emptyList(),
null),
null,
null,
"B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=");
final PrivateSubscribeRequest subscribeRequest =
mapper.mapPrivateSubscribeRequest(new JsonRpcRequestContext(jsonRpcRequest));
assertThat(subscribeRequest)
.isEqualToComparingFieldByFieldRecursively(expectedSubscribeRequest);
}
@Test
public void mapRequestToPrivateSubscriptionWithInvalidType() {
final JsonRpcRequest jsonRpcRequest =
parseWebSocketRpcRequest(
"{\"id\": 1, \"method\": \"priv_subscribe\", \"params\": [\"B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=\", \"syncing\", {\"includeTransactions\": true}]}");
thrown.expect(InvalidSubscriptionRequestException.class);
thrown.expectMessage("Invalid subscribe request. Invalid private subscription type.");
mapper.mapPrivateSubscribeRequest(new JsonRpcRequestContext(jsonRpcRequest));
}
@Test
public void mapRequestToPrivateUnsubscribeRequest() {
final JsonRpcRequest jsonRpcRequest =
parseWebSocketRpcRequest(
"{\"id\": 1, \"method\": \"priv_unsubscribe\", \"params\": [\"B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=\", \"0x1\"]}");
final PrivateUnsubscribeRequest expectedUnsubscribeRequest =
new PrivateUnsubscribeRequest(
1L, CONNECTION_ID, "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=");
final PrivateUnsubscribeRequest unsubscribeRequest =
mapper.mapPrivateUnsubscribeRequest(new JsonRpcRequestContext(jsonRpcRequest));
assertThat(unsubscribeRequest).isEqualTo(expectedUnsubscribeRequest);
}
private WebSocketRpcRequest parseWebSocketRpcRequest(final String json) {
return Json.decodeValue(json, WebSocketRpcRequest.class);
}

@ -1 +1 @@
Subproject commit 5841af6da472fb3f19810354cf9a30afd8e72b5f
Subproject commit 6af0621522dd0274525457741291d391c10002be
Loading…
Cancel
Save