diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index 7dcda7fdfa..6888ff32c1 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.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.blockcreation.MiningCoordinator; 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.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; 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.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; @@ -436,6 +439,8 @@ public class RunnerBuilder { .build(); vertx.deployVerticle(filterManager); + createPrivateTransactionObserver(filterManager, privacyParameters); + final P2PNetwork peerNetwork = networkRunner.getNetwork(); 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( final Synchronizer synchronizer, final SubscriptionManager subscriptionManager) { new SyncingSubscriptionService(subscriptionManager, synchronizer); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManager.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManager.java index 0164e31ec3..1bd766f757 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManager.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManager.java @@ -27,6 +27,8 @@ import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.core.Transaction; 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.Collection; @@ -37,7 +39,7 @@ import com.google.common.annotations.VisibleForTesting; import io.vertx.core.AbstractVerticle; /** 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; @@ -45,6 +47,7 @@ public class FilterManager extends AbstractVerticle { private final FilterRepository filterRepository; private final BlockchainQueries blockchainQueries; private final Optional privacyQueries; + private final List removalEvents; FilterManager( final BlockchainQueries blockchainQueries, @@ -59,6 +62,7 @@ public class FilterManager extends AbstractVerticle { transactionPool.subscribePendingTransactions(this::recordPendingTransactionEvent); this.blockchainQueries = blockchainQueries; this.privacyQueries = privacyQueries; + this.removalEvents = new ArrayList<>(); } @Override @@ -120,6 +124,7 @@ public class FilterManager extends AbstractVerticle { * Installs a new private log filter * * @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 toBlock {@link BlockParameter} Integer block number, or latest/pending/earliest. * @param logsQuery {@link LogsQuery} Addresses and/or topics to filter by @@ -127,12 +132,14 @@ public class FilterManager extends AbstractVerticle { */ public String installPrivateLogFilter( final String privacyGroupId, + final String enclavePublicKey, final BlockParameter fromBlock, final BlockParameter toBlock, final LogsQuery logsQuery) { final String filterId = filterIdGenerator.nextId(); filterRepository.save( - new PrivateLogFilter(filterId, privacyGroupId, fromBlock, toBlock, logsQuery)); + new PrivateLogFilter( + filterId, privacyGroupId, enclavePublicKey, fromBlock, toBlock, logsQuery)); return filterId; } @@ -162,6 +169,9 @@ public class FilterManager extends AbstractVerticle { } }); + removalEvents.stream().forEach(removalEvent -> processRemovalEvent(removalEvent)); + removalEvents.clear(); + final List logsWithMetadata = event.getLogsWithMetadata(); filterRepository.getFiltersOfType(LogFilter.class).stream() .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 void recordPendingTransactionEvent(final Transaction transaction) { final Collection 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. * diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/PrivateLogFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/PrivateLogFilter.java index 7b03e9b418..87836b5ff0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/PrivateLogFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/PrivateLogFilter.java @@ -20,18 +20,25 @@ import org.hyperledger.besu.ethereum.api.query.LogsQuery; public class PrivateLogFilter extends LogFilter { private final String privacyGroupId; + private final String enclavePublicKey; PrivateLogFilter( final String id, final String privacyGroupId, + final String enclavePublicKey, final BlockParameter fromBlock, final BlockParameter toBlock, final LogsQuery logsQuery) { super(id, fromBlock, toBlock, logsQuery); this.privacyGroupId = privacyGroupId; + this.enclavePublicKey = enclavePublicKey; } public String getPrivacyGroupId() { return privacyGroupId; } + + public String getEnclavePublicKey() { + return enclavePublicKey; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java index ee8233c1c1..ea5cadde70 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java @@ -98,7 +98,7 @@ public class EeaSendRawTransaction implements JsonRpcMethod { if (maybePrivacyGroup.isEmpty()) { return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); } - } else { // !onchainPirvacyGroupEnabled + } else { // !onchainPrivacyGroupEnabled if (maybePrivacyGroupId.isPresent()) { maybePrivacyGroup = privacyController.retrieveOffChainPrivacyGroup( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java index a12814d6ac..8b16affdc3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java @@ -93,7 +93,7 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod { if (maybePrivacyGroup.isEmpty()) { return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); } - } else { // !onchainPirvacyGroupEnabled + } else { // !onchainPrivacyGroupEnabled if (maybePrivacyGroupId.isPresent()) { maybePrivacyGroup = privacyController.retrieveOffChainPrivacyGroup( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java index 5642ba4a51..8788709660 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java @@ -50,8 +50,10 @@ public class PrivNewFilter implements JsonRpcMethod { public JsonRpcResponse response(final JsonRpcRequestContext request) { final String privacyGroupId = request.getRequiredParameter(0, String.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()) { return new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); @@ -59,14 +61,17 @@ public class PrivNewFilter implements JsonRpcMethod { final String logFilterId = filterManager.installPrivateLogFilter( - privacyGroupId, filter.getFromBlock(), filter.getToBlock(), filter.getLogsQuery()); + privacyGroupId, + enclavePublicKey, + filter.getFromBlock(), + filter.getToBlock(), + filter.getLogsQuery()); return new JsonRpcSuccessResponse(request.getRequest().getId(), logFilterId); } private void checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey( - final JsonRpcRequestContext request, final String privacyGroupId) { - final String enclavePublicKey = enclavePublicKeyProvider.getEnclaveKey(request.getUser()); + final String enclavePublicKey, final String privacyGroupId) { privacyController.verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManagerLogFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManagerLogFilterTest.java index 772d2b4b67..a56748d451 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManagerLogFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/FilterManagerLogFilterTest.java @@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent; import java.util.List; import java.util.Optional; @@ -59,6 +60,7 @@ import org.mockito.junit.MockitoJUnitRunner; public class FilterManagerLogFilterTest { private static final String PRIVACY_GROUP_ID = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; + private static final String ENCLAVE_PUBLIC_KEY = "iOCzoGo5kwtZU0J41Z9xnGXHN6ZNukIa9MspvHtu3Jk="; private FilterManager filterManager; @@ -93,7 +95,8 @@ public class FilterManagerLogFilterTest { @Test 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()); final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash(); @@ -104,7 +107,7 @@ public class FilterManagerLogFilterTest { @Test public void shouldUseBlockHashWhenCheckingLogsForChangesForPrivateFiltersOnly() { 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()); final Hash blockAddedHash = recordBlockEvents(1).get(0).getBlock().getHash(); @@ -222,7 +225,8 @@ public class FilterManagerLogFilterTest { @Test public void installAndUninstallPrivateFilter() { 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(); @@ -239,7 +243,8 @@ public class FilterManagerLogFilterTest { .thenReturn(singletonList(logWithMetadata)); 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(); verify(privacyQueries).matchingLogs(eq(PRIVACY_GROUP_ID), eq(blockAddedHash), eq(logsQuery)); @@ -253,7 +258,8 @@ public class FilterManagerLogFilterTest { .thenReturn(Lists.newArrayList(logWithMetadata)); final String privateLogFilterId = - filterManager.installPrivateLogFilter(PRIVACY_GROUP_ID, latest(), latest(), logsQuery()); + filterManager.installPrivateLogFilter( + PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY, latest(), latest(), logsQuery()); final List logs = filterManager.logs(privateLogFilterId); @@ -264,6 +270,36 @@ public class FilterManagerLogFilterTest { 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 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() { return new LogWithMetadata( 0, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java index b788558440..23440843bb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java @@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Collections; import java.util.List; +import java.util.Optional; import io.vertx.ext.auth.User; import org.apache.tuweni.bytes.Bytes32; @@ -115,7 +116,9 @@ public class PrivNewFilterTest { @Test public void filterWithExpectedQueryIsCreated() { final List
addresses = List.of(Address.ZERO); + final User user = mock(User.class); final List> logTopics = List.of(List.of(LogTopic.of(Bytes32.random()))); + when(enclavePublicKeyProvider.getEnclaveKey(eq(Optional.of(user)))).thenReturn(ENCLAVE_KEY); final FilterParameter filter = new FilterParameter( @@ -124,12 +127,14 @@ public class PrivNewFilterTest { final LogsQuery expectedQuery = 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); verify(filterManager) .installPrivateLogFilter( eq(PRIVACY_GROUP_ID), + eq(ENCLAVE_KEY), refEq(BlockParameter.EARLIEST), refEq(BlockParameter.LATEST), eq((expectedQuery))); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java index 5f10d25667..38da445eff 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java @@ -34,6 +34,8 @@ import org.hyperledger.besu.ethereum.core.WorldUpdater; import org.hyperledger.besu.ethereum.debug.TraceOptions; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; 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.Restriction; 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.MessageFrame; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.util.Subscribers; import java.util.ArrayList; import java.util.Base64; @@ -65,6 +68,9 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac private static final SECP256K1.Signature FAKE_SIGNATURE = SECP256K1.Signature.create(SECP256K1.HALF_CURVE_ORDER, SECP256K1.HALF_CURVE_ORDER, (byte) 0); + private final Subscribers privateTransactionEventObservers = + Subscribers.create(); + private static final Logger LOG = LogManager.getLogger(); public OnChainPrivacyPrecompiledContract( @@ -87,6 +93,14 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac "OnChainPrivacy"); } + public long addPrivateTransactionObserver(final PrivateTransactionObserver observer) { + return privateTransactionEventObservers.subscribe(observer); + } + + public boolean removePrivateTransactionObserver(final long observerId) { + return privateTransactionEventObservers.unsubscribe(observerId); + } + @Override public Bytes compute(final Bytes input, final MessageFrame messageFrame) { @@ -176,6 +190,8 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac return Bytes.EMPTY; } + sendParticipantRemovedEvent(privateTransaction); + if (messageFrame.isPersistingPrivateState()) { persistPrivateState( pmtHash, @@ -189,6 +205,20 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac 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( final MessageFrame messageFrame, final ProcessableBlockHeader currentBlockHeader, @@ -323,6 +353,10 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac return participants; } + private String getRemovedParticipantFromParameter(final Bytes input) { + return input.slice(4).toBase64String(); + } + private List decodeList(final Bytes rlpEncodedList) { final ArrayList decodedElements = new ArrayList<>(); // first 32 bytes is dynamic list offset diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java index 7237f6c1f1..86f4cb94f5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.privacy; import static com.google.common.base.Preconditions.checkState; 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.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) { return item.nextIsList() ? item.readList(RLPInput::readBytes) : item.readBytes(); } @@ -356,7 +365,7 @@ public class PrivateTransaction { .orElseThrow( () -> 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())); } return sender; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java new file mode 100644 index 0000000000..7d088dbcab --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java @@ -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; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java new file mode 100644 index 0000000000..9168d45e73 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java @@ -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); +}