Private Log Filters track enclavePublicKey and are removed when user removed from group (#1298)

* added isRemovalTransaction

Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>

* added enclavePublicKey to private log filter
* store removal events and process with addBlockEvent

Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
pull/1328/head
Sally MacFarlane 4 years ago committed by GitHub
parent 3f64a14432
commit b66d2e67bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  2. 34
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManager.java
  3. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/PrivateLogFilter.java
  4. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java
  5. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java
  6. 13
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java
  7. 46
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManagerLogFilterTest.java
  8. 7
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java
  9. 34
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java
  10. 11
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java
  11. 34
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java
  12. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java

@ -54,11 +54,14 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.PrivacyQueries; import org.hyperledger.besu.ethereum.api.query.PrivacyQueries;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.OnChainPrivacyPrecompiledContract;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration;
@ -436,6 +439,8 @@ public class RunnerBuilder {
.build(); .build();
vertx.deployVerticle(filterManager); vertx.deployVerticle(filterManager);
createPrivateTransactionObserver(filterManager, privacyParameters);
final P2PNetwork peerNetwork = networkRunner.getNetwork(); final P2PNetwork peerNetwork = networkRunner.getNetwork();
final MiningParameters miningParameters = besuController.getMiningParameters(); final MiningParameters miningParameters = besuController.getMiningParameters();
@ -789,6 +794,23 @@ public class RunnerBuilder {
} }
} }
private void createPrivateTransactionObserver(
final FilterManager filterManager, final PrivacyParameters privacyParameters) {
// register filterManager as observer of events fired by the onchain precompile.
// filterManager needs to remove filters when the creator is removed from onchain group
if (privacyParameters.isOnchainPrivacyGroupsEnabled()
&& privacyParameters.isMultiTenancyEnabled()) {
final OnChainPrivacyPrecompiledContract onchainPrivacyPrecompiledContract =
(OnChainPrivacyPrecompiledContract)
besuController
.getProtocolSchedule()
.getByBlockNumber(1)
.getPrecompileContractRegistry()
.get(Address.ONCHAIN_PRIVACY, Account.DEFAULT_VERSION);
onchainPrivacyPrecompiledContract.addPrivateTransactionObserver(filterManager);
}
}
private void createSyncingSubscriptionService( private void createSyncingSubscriptionService(
final Synchronizer synchronizer, final SubscriptionManager subscriptionManager) { final Synchronizer synchronizer, final SubscriptionManager subscriptionManager) {
new SyncingSubscriptionService(subscriptionManager, synchronizer); new SyncingSubscriptionService(subscriptionManager, synchronizer);

@ -27,6 +27,8 @@ import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionObserver;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -37,7 +39,7 @@ import com.google.common.annotations.VisibleForTesting;
import io.vertx.core.AbstractVerticle; import io.vertx.core.AbstractVerticle;
/** Manages JSON-RPC filter events. */ /** Manages JSON-RPC filter events. */
public class FilterManager extends AbstractVerticle { public class FilterManager extends AbstractVerticle implements PrivateTransactionObserver {
private static final int FILTER_TIMEOUT_CHECK_TIMER = 10000; private static final int FILTER_TIMEOUT_CHECK_TIMER = 10000;
@ -45,6 +47,7 @@ public class FilterManager extends AbstractVerticle {
private final FilterRepository filterRepository; private final FilterRepository filterRepository;
private final BlockchainQueries blockchainQueries; private final BlockchainQueries blockchainQueries;
private final Optional<PrivacyQueries> privacyQueries; private final Optional<PrivacyQueries> privacyQueries;
private final List<PrivateTransactionEvent> removalEvents;
FilterManager( FilterManager(
final BlockchainQueries blockchainQueries, final BlockchainQueries blockchainQueries,
@ -59,6 +62,7 @@ public class FilterManager extends AbstractVerticle {
transactionPool.subscribePendingTransactions(this::recordPendingTransactionEvent); transactionPool.subscribePendingTransactions(this::recordPendingTransactionEvent);
this.blockchainQueries = blockchainQueries; this.blockchainQueries = blockchainQueries;
this.privacyQueries = privacyQueries; this.privacyQueries = privacyQueries;
this.removalEvents = new ArrayList<>();
} }
@Override @Override
@ -120,6 +124,7 @@ public class FilterManager extends AbstractVerticle {
* Installs a new private log filter * Installs a new private log filter
* *
* @param privacyGroupId String privacyGroupId * @param privacyGroupId String privacyGroupId
* @param enclavePublicKey String enclavePublicKey of user creating the filter
* @param fromBlock {@link BlockParameter} Integer block number, or latest/pending/earliest. * @param fromBlock {@link BlockParameter} Integer block number, or latest/pending/earliest.
* @param toBlock {@link BlockParameter} Integer block number, or latest/pending/earliest. * @param toBlock {@link BlockParameter} Integer block number, or latest/pending/earliest.
* @param logsQuery {@link LogsQuery} Addresses and/or topics to filter by * @param logsQuery {@link LogsQuery} Addresses and/or topics to filter by
@ -127,12 +132,14 @@ public class FilterManager extends AbstractVerticle {
*/ */
public String installPrivateLogFilter( public String installPrivateLogFilter(
final String privacyGroupId, final String privacyGroupId,
final String enclavePublicKey,
final BlockParameter fromBlock, final BlockParameter fromBlock,
final BlockParameter toBlock, final BlockParameter toBlock,
final LogsQuery logsQuery) { final LogsQuery logsQuery) {
final String filterId = filterIdGenerator.nextId(); final String filterId = filterIdGenerator.nextId();
filterRepository.save( filterRepository.save(
new PrivateLogFilter(filterId, privacyGroupId, fromBlock, toBlock, logsQuery)); new PrivateLogFilter(
filterId, privacyGroupId, enclavePublicKey, fromBlock, toBlock, logsQuery));
return filterId; return filterId;
} }
@ -162,6 +169,9 @@ public class FilterManager extends AbstractVerticle {
} }
}); });
removalEvents.stream().forEach(removalEvent -> processRemovalEvent(removalEvent));
removalEvents.clear();
final List<LogWithMetadata> logsWithMetadata = event.getLogsWithMetadata(); final List<LogWithMetadata> logsWithMetadata = event.getLogsWithMetadata();
filterRepository.getFiltersOfType(LogFilter.class).stream() filterRepository.getFiltersOfType(LogFilter.class).stream()
.filter( .filter(
@ -192,6 +202,12 @@ public class FilterManager extends AbstractVerticle {
}); });
} }
@Override
public void onPrivateTransactionProcessed(final PrivateTransactionEvent event) {
// the list will be processed at the end of the block
removalEvents.add(event);
}
@VisibleForTesting @VisibleForTesting
void recordPendingTransactionEvent(final Transaction transaction) { void recordPendingTransactionEvent(final Transaction transaction) {
final Collection<PendingTransactionFilter> pendingTransactionFilters = final Collection<PendingTransactionFilter> pendingTransactionFilters =
@ -208,6 +224,20 @@ public class FilterManager extends AbstractVerticle {
}); });
} }
@VisibleForTesting
void processRemovalEvent(final PrivateTransactionEvent event) {
// when user removed from privacy group, remove all filters created by that user in that group
filterRepository.getFiltersOfType(PrivateLogFilter.class).stream()
.filter(
privateLogFilter ->
privateLogFilter.getPrivacyGroupId().equals(event.getPrivacyGroupId())
&& privateLogFilter.getEnclavePublicKey().equals(event.getEnclavePublicKey()))
.forEach(
privateLogFilter -> {
uninstallFilter(privateLogFilter.getId());
});
}
/** /**
* Gets the new block hashes that have occurred since the filter was last checked. * Gets the new block hashes that have occurred since the filter was last checked.
* *

@ -20,18 +20,25 @@ import org.hyperledger.besu.ethereum.api.query.LogsQuery;
public class PrivateLogFilter extends LogFilter { public class PrivateLogFilter extends LogFilter {
private final String privacyGroupId; private final String privacyGroupId;
private final String enclavePublicKey;
PrivateLogFilter( PrivateLogFilter(
final String id, final String id,
final String privacyGroupId, final String privacyGroupId,
final String enclavePublicKey,
final BlockParameter fromBlock, final BlockParameter fromBlock,
final BlockParameter toBlock, final BlockParameter toBlock,
final LogsQuery logsQuery) { final LogsQuery logsQuery) {
super(id, fromBlock, toBlock, logsQuery); super(id, fromBlock, toBlock, logsQuery);
this.privacyGroupId = privacyGroupId; this.privacyGroupId = privacyGroupId;
this.enclavePublicKey = enclavePublicKey;
} }
public String getPrivacyGroupId() { public String getPrivacyGroupId() {
return privacyGroupId; return privacyGroupId;
} }
public String getEnclavePublicKey() {
return enclavePublicKey;
}
} }

@ -98,7 +98,7 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
if (maybePrivacyGroup.isEmpty()) { if (maybePrivacyGroup.isEmpty()) {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST);
} }
} else { // !onchainPirvacyGroupEnabled } else { // !onchainPrivacyGroupEnabled
if (maybePrivacyGroupId.isPresent()) { if (maybePrivacyGroupId.isPresent()) {
maybePrivacyGroup = maybePrivacyGroup =
privacyController.retrieveOffChainPrivacyGroup( privacyController.retrieveOffChainPrivacyGroup(

@ -93,7 +93,7 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod {
if (maybePrivacyGroup.isEmpty()) { if (maybePrivacyGroup.isEmpty()) {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST);
} }
} else { // !onchainPirvacyGroupEnabled } else { // !onchainPrivacyGroupEnabled
if (maybePrivacyGroupId.isPresent()) { if (maybePrivacyGroupId.isPresent()) {
maybePrivacyGroup = maybePrivacyGroup =
privacyController.retrieveOffChainPrivacyGroup( privacyController.retrieveOffChainPrivacyGroup(

@ -50,8 +50,10 @@ public class PrivNewFilter implements JsonRpcMethod {
public JsonRpcResponse response(final JsonRpcRequestContext request) { public JsonRpcResponse response(final JsonRpcRequestContext request) {
final String privacyGroupId = request.getRequiredParameter(0, String.class); final String privacyGroupId = request.getRequiredParameter(0, String.class);
final FilterParameter filter = request.getRequiredParameter(1, FilterParameter.class); final FilterParameter filter = request.getRequiredParameter(1, FilterParameter.class);
final String enclavePublicKey = enclavePublicKeyProvider.getEnclaveKey(request.getUser());
checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(request, privacyGroupId); // no need to pass blockNumber. To create a filter, you need to be a current member of the group
checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(enclavePublicKey, privacyGroupId);
if (!filter.isValid()) { if (!filter.isValid()) {
return new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); return new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
@ -59,14 +61,17 @@ public class PrivNewFilter implements JsonRpcMethod {
final String logFilterId = final String logFilterId =
filterManager.installPrivateLogFilter( filterManager.installPrivateLogFilter(
privacyGroupId, filter.getFromBlock(), filter.getToBlock(), filter.getLogsQuery()); privacyGroupId,
enclavePublicKey,
filter.getFromBlock(),
filter.getToBlock(),
filter.getLogsQuery());
return new JsonRpcSuccessResponse(request.getRequest().getId(), logFilterId); return new JsonRpcSuccessResponse(request.getRequest().getId(), logFilterId);
} }
private void checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey( private void checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(
final JsonRpcRequestContext request, final String privacyGroupId) { final String enclavePublicKey, final String privacyGroupId) {
final String enclavePublicKey = enclavePublicKeyProvider.getEnclaveKey(request.getUser());
privacyController.verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey); privacyController.verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey);
} }
} }

@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -59,6 +60,7 @@ import org.mockito.junit.MockitoJUnitRunner;
public class FilterManagerLogFilterTest { public class FilterManagerLogFilterTest {
private static final String PRIVACY_GROUP_ID = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; private static final String PRIVACY_GROUP_ID = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=";
private static final String ENCLAVE_PUBLIC_KEY = "iOCzoGo5kwtZU0J41Z9xnGXHN6ZNukIa9MspvHtu3Jk=";
private FilterManager filterManager; private FilterManager filterManager;
@ -93,7 +95,8 @@ public class FilterManagerLogFilterTest {
@Test @Test
public void shouldCheckMatchingLogsWhenRecordedNewBlockEventForPrivateFiltersOnly() { public void shouldCheckMatchingLogsWhenRecordedNewBlockEventForPrivateFiltersOnly() {
filterManager.installPrivateLogFilter(PRIVACY_GROUP_ID, latest(), latest(), logsQuery()); filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery());
filterManager.installLogFilter(latest(), latest(), logsQuery()); filterManager.installLogFilter(latest(), latest(), logsQuery());
final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash(); final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash();
@ -104,7 +107,7 @@ public class FilterManagerLogFilterTest {
@Test @Test
public void shouldUseBlockHashWhenCheckingLogsForChangesForPrivateFiltersOnly() { public void shouldUseBlockHashWhenCheckingLogsForChangesForPrivateFiltersOnly() {
filterManager.installPrivateLogFilter( filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, blockNum(1L), blockNum(10L), logsQuery()); PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, blockNum(1L), blockNum(10L), logsQuery());
filterManager.installLogFilter(blockNum(1L), blockNum(10L), logsQuery()); filterManager.installLogFilter(blockNum(1L), blockNum(10L), logsQuery());
final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash(); final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash();
@ -222,7 +225,8 @@ public class FilterManagerLogFilterTest {
@Test @Test
public void installAndUninstallPrivateFilter() { public void installAndUninstallPrivateFilter() {
final String filterId = final String filterId =
filterManager.installPrivateLogFilter(PRIVACY_GROUP_ID, latest(), latest(), logsQuery()); filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery());
assertThat(filterRepository.getFilter(filterId, PrivateLogFilter.class)).isPresent(); assertThat(filterRepository.getFilter(filterId, PrivateLogFilter.class)).isPresent();
@ -239,7 +243,8 @@ public class FilterManagerLogFilterTest {
.thenReturn(singletonList(logWithMetadata)); .thenReturn(singletonList(logWithMetadata));
final String filterId = final String filterId =
filterManager.installPrivateLogFilter(PRIVACY_GROUP_ID, latest(), latest(), logsQuery); filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery);
final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash(); final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash();
verify(privacyQueries).matchingLogs(eq(PRIVACY_GROUP_ID), eq(blockAddedHash), eq(logsQuery)); verify(privacyQueries).matchingLogs(eq(PRIVACY_GROUP_ID), eq(blockAddedHash), eq(logsQuery));
@ -253,7 +258,8 @@ public class FilterManagerLogFilterTest {
.thenReturn(Lists.newArrayList(logWithMetadata)); .thenReturn(Lists.newArrayList(logWithMetadata));
final String privateLogFilterId = final String privateLogFilterId =
filterManager.installPrivateLogFilter(PRIVACY_GROUP_ID, latest(), latest(), logsQuery()); filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery());
final List<LogWithMetadata> logs = filterManager.logs(privateLogFilterId); final List<LogWithMetadata> logs = filterManager.logs(privateLogFilterId);
@ -264,6 +270,36 @@ public class FilterManagerLogFilterTest {
assertThat(logs.get(0)).isEqualTo(logWithMetadata); assertThat(logs.get(0)).isEqualTo(logWithMetadata);
} }
@Test
public void removalEvent_uninstallsFilter() {
final LogWithMetadata logWithMetadata = logWithMetadata();
when(privacyQueries.matchingLogs(eq(PRIVACY_GROUP_ID), anyLong(), anyLong(), any()))
.thenReturn(Lists.newArrayList(logWithMetadata));
final String privateLogFilterId =
filterManager.installPrivateLogFilter(
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery());
final List<LogWithMetadata> logs = filterManager.logs(privateLogFilterId);
verify(blockchainQueries, times(2)).headBlockNumber();
verify(blockchainQueries, never()).matchingLogs(anyLong(), anyLong(), any(), any());
verify(privacyQueries).matchingLogs(eq(PRIVACY_GROUP_ID), anyLong(), anyLong(), any());
assertThat(logs.get(0)).isEqualTo(logWithMetadata);
// signal removal of user from group
privateTransactionEvent(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY);
assertThat(filterManager.logs(privateLogFilterId)).isNull();
assertThat(filterRepository.getFilter(privateLogFilterId, PrivateLogFilter.class)).isEmpty();
}
private void privateTransactionEvent(final String privacyGroupId, final String enclavePublicKey) {
PrivateTransactionEvent event = new PrivateTransactionEvent(privacyGroupId, enclavePublicKey);
filterManager.processRemovalEvent(event);
}
private LogWithMetadata logWithMetadata() { private LogWithMetadata logWithMetadata() {
return new LogWithMetadata( return new LogWithMetadata(
0, 0,

@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import io.vertx.ext.auth.User; import io.vertx.ext.auth.User;
import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.Bytes32;
@ -115,7 +116,9 @@ public class PrivNewFilterTest {
@Test @Test
public void filterWithExpectedQueryIsCreated() { public void filterWithExpectedQueryIsCreated() {
final List<Address> addresses = List.of(Address.ZERO); final List<Address> addresses = List.of(Address.ZERO);
final User user = mock(User.class);
final List<List<LogTopic>> logTopics = List.of(List.of(LogTopic.of(Bytes32.random()))); final List<List<LogTopic>> logTopics = List.of(List.of(LogTopic.of(Bytes32.random())));
when(enclavePublicKeyProvider.getEnclaveKey(eq(Optional.of(user)))).thenReturn(ENCLAVE_KEY);
final FilterParameter filter = final FilterParameter filter =
new FilterParameter( new FilterParameter(
@ -124,12 +127,14 @@ public class PrivNewFilterTest {
final LogsQuery expectedQuery = final LogsQuery expectedQuery =
new LogsQuery.Builder().addresses(addresses).topics(logTopics).build(); new LogsQuery.Builder().addresses(addresses).topics(logTopics).build();
final JsonRpcRequestContext request = privNewFilterRequest(PRIVACY_GROUP_ID, filter); final JsonRpcRequestContext request =
privNewFilterRequestWithUser(PRIVACY_GROUP_ID, filter, user);
method.response(request); method.response(request);
verify(filterManager) verify(filterManager)
.installPrivateLogFilter( .installPrivateLogFilter(
eq(PRIVACY_GROUP_ID), eq(PRIVACY_GROUP_ID),
eq(ENCLAVE_KEY),
refEq(BlockParameter.EARLIEST), refEq(BlockParameter.EARLIEST),
refEq(BlockParameter.LATEST), refEq(BlockParameter.LATEST),
eq((expectedQuery))); eq((expectedQuery)));

@ -34,6 +34,8 @@ import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionObserver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.Restriction; import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction; import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
@ -46,6 +48,7 @@ import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.GasCalculator;
import org.hyperledger.besu.ethereum.vm.MessageFrame; import org.hyperledger.besu.ethereum.vm.MessageFrame;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.util.Subscribers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
@ -65,6 +68,9 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
private static final SECP256K1.Signature FAKE_SIGNATURE = private static final SECP256K1.Signature FAKE_SIGNATURE =
SECP256K1.Signature.create(SECP256K1.HALF_CURVE_ORDER, SECP256K1.HALF_CURVE_ORDER, (byte) 0); SECP256K1.Signature.create(SECP256K1.HALF_CURVE_ORDER, SECP256K1.HALF_CURVE_ORDER, (byte) 0);
private final Subscribers<PrivateTransactionObserver> privateTransactionEventObservers =
Subscribers.create();
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
public OnChainPrivacyPrecompiledContract( public OnChainPrivacyPrecompiledContract(
@ -87,6 +93,14 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
"OnChainPrivacy"); "OnChainPrivacy");
} }
public long addPrivateTransactionObserver(final PrivateTransactionObserver observer) {
return privateTransactionEventObservers.subscribe(observer);
}
public boolean removePrivateTransactionObserver(final long observerId) {
return privateTransactionEventObservers.unsubscribe(observerId);
}
@Override @Override
public Bytes compute(final Bytes input, final MessageFrame messageFrame) { public Bytes compute(final Bytes input, final MessageFrame messageFrame) {
@ -176,6 +190,8 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
return Bytes.EMPTY; return Bytes.EMPTY;
} }
sendParticipantRemovedEvent(privateTransaction);
if (messageFrame.isPersistingPrivateState()) { if (messageFrame.isPersistingPrivateState()) {
persistPrivateState( persistPrivateState(
pmtHash, pmtHash,
@ -189,6 +205,20 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
return result.getOutput(); return result.getOutput();
} }
private void sendParticipantRemovedEvent(final PrivateTransaction privateTransaction) {
if (privateTransaction.isGroupRemovalTransaction()) {
// get first participant parameter - there is only one for removal transaction
final String removedParticipant =
getRemovedParticipantFromParameter(privateTransaction.getPayload());
final PrivateTransactionEvent removalEvent =
new PrivateTransactionEvent(
privateTransaction.getPrivacyGroupId().get().toBase64String(), removedParticipant);
privateTransactionEventObservers.forEach(
sub -> sub.onPrivateTransactionProcessed(removalEvent));
}
}
boolean canExecute( boolean canExecute(
final MessageFrame messageFrame, final MessageFrame messageFrame,
final ProcessableBlockHeader currentBlockHeader, final ProcessableBlockHeader currentBlockHeader,
@ -323,6 +353,10 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
return participants; return participants;
} }
private String getRemovedParticipantFromParameter(final Bytes input) {
return input.slice(4).toBase64String();
}
private List<Bytes> decodeList(final Bytes rlpEncodedList) { private List<Bytes> decodeList(final Bytes rlpEncodedList) {
final ArrayList<Bytes> decodedElements = new ArrayList<>(); final ArrayList<Bytes> decodedElements = new ArrayList<>();
// first 32 bytes is dynamic list offset // first 32 bytes is dynamic list offset

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.privacy;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static org.hyperledger.besu.crypto.Hash.keccak256; import static org.hyperledger.besu.crypto.Hash.keccak256;
import static org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement.REMOVE_PARTICIPANT_METHOD_SIGNATURE;
import org.hyperledger.besu.crypto.SECP256K1; import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
@ -149,6 +150,14 @@ public class PrivateTransaction {
} }
} }
public boolean isGroupRemovalTransaction() {
return this.getTo().isPresent()
&& this.getTo().get().equals(Address.ONCHAIN_PRIVACY_PROXY)
&& this.getPayload()
.toHexString()
.startsWith(REMOVE_PARTICIPANT_METHOD_SIGNATURE.toHexString());
}
private static Object resolvePrivateForOrPrivacyGroupId(final RLPInput item) { private static Object resolvePrivateForOrPrivacyGroupId(final RLPInput item) {
return item.nextIsList() ? item.readList(RLPInput::readBytes) : item.readBytes(); return item.nextIsList() ? item.readList(RLPInput::readBytes) : item.readBytes();
} }
@ -356,7 +365,7 @@ public class PrivateTransaction {
.orElseThrow( .orElseThrow(
() -> () ->
new IllegalStateException( new IllegalStateException(
"Cannot recover public key from " + "signature for " + this)); "Cannot recover public key from signature for " + this));
sender = Address.extract(Hash.hash(publicKey.getEncodedBytes())); sender = Address.extract(Hash.hash(publicKey.getEncodedBytes()));
} }
return sender; return sender;

@ -0,0 +1,34 @@
/*
* 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.privacy;
public class PrivateTransactionEvent {
private final String privacyGroupId;
private final String enclavePublicKey;
public PrivateTransactionEvent(final String privacyGroupId, final String enclavePublicKey) {
this.privacyGroupId = privacyGroupId;
this.enclavePublicKey = enclavePublicKey;
}
public String getPrivacyGroupId() {
return privacyGroupId;
}
public String getEnclavePublicKey() {
return enclavePublicKey;
}
}

@ -0,0 +1,21 @@
/*
* 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.privacy;
@FunctionalInterface
public interface PrivateTransactionObserver {
void onPrivateTransactionProcessed(PrivateTransactionEvent event);
}
Loading…
Cancel
Save