diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java index bdb6a9728e..d3cd6bf638 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java @@ -70,7 +70,32 @@ public class PrivacyTest { } @Test - public void privacyPrecompiled() throws IOException, URISyntaxException { + public void defaultPrivacy() throws IOException, URISyntaxException { + final BesuController besuController = setUpControllerWithPrivacyEnabled(false); + + final PrecompiledContract precompiledContract = + getPrecompile(besuController, Address.DEFAULT_PRIVACY); + + assertThat(precompiledContract.getName()).isEqualTo("Privacy"); + } + + @Test + public void onchainEnabledPrivacy() throws IOException, URISyntaxException { + final BesuController besuController = setUpControllerWithPrivacyEnabled(true); + + final PrecompiledContract privacyPrecompiledContract = + getPrecompile(besuController, Address.DEFAULT_PRIVACY); + + assertThat(privacyPrecompiledContract.getName()).isEqualTo("Privacy"); + + final PrecompiledContract onchainPrecompiledContract = + getPrecompile(besuController, Address.ONCHAIN_PRIVACY); + + assertThat(onchainPrecompiledContract.getName()).isEqualTo("OnChainPrivacy"); + } + + private BesuController setUpControllerWithPrivacyEnabled(final boolean onChainEnabled) + throws IOException, URISyntaxException { final Path dataDir = folder.newFolder().toPath(); final Path dbDir = dataDir.resolve("database"); final PrivacyParameters privacyParameters = @@ -80,33 +105,23 @@ public class PrivacyTest { .setEnclaveUrl(new URI("http://127.0.0.1:8000")) .setStorageProvider(createKeyValueStorageProvider(dataDir, dbDir)) .setEnclaveFactory(new EnclaveFactory(vertx)) + .setOnchainPrivacyGroupsEnabled(onChainEnabled) .build(); - final BesuController besuController = - new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet()) - .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .storageProvider(new InMemoryStorageProvider()) - .networkId(BigInteger.ONE) - .miningParameters(new MiningParametersTestBuilder().enabled(false).build()) - .nodeKeys(KeyPair.generate()) - .metricsSystem(new NoOpMetricsSystem()) - .dataDirectory(dataDir) - .clock(TestClock.fixed()) - .privacyParameters(privacyParameters) - .transactionPoolConfiguration(TransactionPoolConfiguration.builder().build()) - .targetGasLimit(GasLimitCalculator.DEFAULT) - .build(); - - final Address privacyContractAddress = Address.DEFAULT_PRIVACY; - final PrecompiledContract precompiledContract = - besuController - .getProtocolSchedule() - .getByBlockNumber(1) - .getPrecompileContractRegistry() - .get(privacyContractAddress, Account.DEFAULT_VERSION); - - assertThat(precompiledContract.getName()).isEqualTo("Privacy"); + return new BesuController.Builder() + .fromGenesisConfig(GenesisConfigFile.mainnet()) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(new MiningParametersTestBuilder().enabled(false).build()) + .nodeKeys(KeyPair.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.builder().build()) + .targetGasLimit(GasLimitCalculator.DEFAULT) + .build(); } private PrivacyStorageProvider createKeyValueStorageProvider( @@ -127,4 +142,13 @@ public class PrivacyTest { .withMetricsSystem(new NoOpMetricsSystem()) .build(); } + + private PrecompiledContract getPrecompile( + final BesuController besuController, final Address defaultPrivacy) { + return besuController + .getProtocolSchedule() + .getByBlockNumber(1) + .getPrecompileContractRegistry() + .get(defaultPrivacy, Account.DEFAULT_VERSION); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java index 7f0d2e46e1..40cec1c657 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java @@ -15,14 +15,17 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.privx; import static org.apache.logging.log4j.LogManager.getLogger; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.FIND_ON_CHAIN_PRIVACY_GROUP_ERROR; import org.hyperledger.besu.enclave.types.PrivacyGroup; 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.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider; +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.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Arrays; @@ -56,10 +59,21 @@ public class PrivxFindOnChainPrivacyGroup implements JsonRpcMethod { LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); - final List response = - privacyController.findOnChainPrivacyGroup( - Arrays.asList(addresses), - enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + final List response; + try { + response = + privacyController.findOnChainPrivacyGroup( + Arrays.asList(addresses), + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + } catch (final MultiTenancyValidationException e) { + LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage()); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), FIND_ON_CHAIN_PRIVACY_GROUP_ERROR); + } catch (final Exception e) { + LOG.error("Failed to fetch on chain privacy group", e); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), FIND_ON_CHAIN_PRIVACY_GROUP_ERROR); + } return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java index b9fa287256..b93802c737 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java @@ -111,6 +111,7 @@ public enum JsonRpcError { CREATE_PRIVACY_GROUP_ERROR(-50100, "Error creating privacy group"), DELETE_PRIVACY_GROUP_ERROR(-50100, "Error deleting privacy group"), FIND_PRIVACY_GROUP_ERROR(-50100, "Error finding privacy group"), + FIND_ON_CHAIN_PRIVACY_GROUP_ERROR(-50100, "Error finding on-chain privacy group"), VALUE_NOT_ZERO(-50100, "We cannot transfer ether in private transaction yet."), DECODE_ERROR(-50100, "Unable to decode the private signed raw transaction"), GET_PRIVATE_TRANSACTION_NONCE_ERROR(-50100, "Unable to determine nonce for account in group."), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java index fd75c662c1..1db2844195 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.VALID_BASE64_ENCLAVE_KEY; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,7 +44,7 @@ import org.junit.Before; import org.junit.Test; public class PrivFindPrivacyGroupTest { - private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String ENCLAVE_PUBLIC_KEY = VALID_BASE64_ENCLAVE_KEY.toBase64String(); private static final List ADDRESSES = List.of( "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", @@ -65,7 +66,7 @@ public class PrivFindPrivacyGroupTest { when(privacyParameters.isEnabled()).thenReturn(true); request = new JsonRpcRequestContext( - new JsonRpcRequest("1", "priv_deletePrivacyGroup", new Object[] {ADDRESSES}), user); + new JsonRpcRequest("1", "priv_findPrivacyGroup", new Object[] {ADDRESSES}), user); privacyGroup = new PrivacyGroup(); privacyGroup.setName("privacyGroup"); privacyGroup.setDescription("privacyGroup desc"); @@ -115,5 +116,6 @@ public class PrivFindPrivacyGroupTest { request.getRequest().getId(), JsonRpcError.FIND_PRIVACY_GROUP_ERROR); final JsonRpcResponse response = privFindPrivacyGroup.response(request); assertThat(response).isEqualTo(expectedResponse); + verify(privacyController).findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroupTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroupTest.java new file mode 100644 index 0000000000..e62ba6ffb6 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroupTest.java @@ -0,0 +1,120 @@ +/* + * 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.internal.privacy.methods.privx; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.EnclaveClientException; +import org.hyperledger.besu.enclave.types.PrivacyGroup; +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.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; + +import java.util.Collections; +import java.util.List; + +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.User; +import io.vertx.ext.auth.jwt.impl.JWTUser; +import org.assertj.core.util.Lists; +import org.junit.Before; +import org.junit.Test; + +public class PrivxFindOnChainPrivacyGroupTest { + private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final List ADDRESSES = + List.of( + "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", + "0x627306090abab3a6e1400e9345bc60c78a8bef57"); + + private final Enclave enclave = mock(Enclave.class); + private final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); + private final PrivacyController privacyController = mock(PrivacyController.class); + + private final User user = + new JWTUser(new JsonObject().put("privacyPublicKey", ENCLAVE_PUBLIC_KEY), ""); + private final EnclavePublicKeyProvider enclavePublicKeyProvider = (user) -> ENCLAVE_PUBLIC_KEY; + + private JsonRpcRequestContext request; + private PrivacyGroup privacyGroup; + private PrivxFindOnChainPrivacyGroup privxFindOnChainPrivacyGroup; + + @Before + public void setUp() { + when(privacyParameters.getEnclave()).thenReturn(enclave); + when(privacyParameters.isEnabled()).thenReturn(true); + request = + new JsonRpcRequestContext( + new JsonRpcRequest("1", "privx_findOnChainPrivacyGroup", new Object[] {ADDRESSES}), + user); + privacyGroup = new PrivacyGroup(); + privacyGroup.setName(""); + privacyGroup.setDescription(""); + privacyGroup.setPrivacyGroupId("privacy group id"); + privacyGroup.setMembers(Lists.list("member1")); + + privxFindOnChainPrivacyGroup = + new PrivxFindOnChainPrivacyGroup(privacyController, enclavePublicKeyProvider); + } + + @SuppressWarnings("unchecked") + @Test + public void findsPrivacyGroupWithValidAddresses() { + when(privacyController.findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + .thenReturn(Collections.singletonList(privacyGroup)); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) privxFindOnChainPrivacyGroup.response(request); + final List result = (List) response.getResult(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualToComparingFieldByField(privacyGroup); + verify(privacyController).findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY); + } + + @Test + public void failsWithFindPrivacyGroupErrorIfEnclaveFails() { + when(privacyController.findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + .thenThrow(new EnclaveClientException(500, "some failure")); + + final JsonRpcErrorResponse response = + (JsonRpcErrorResponse) privxFindOnChainPrivacyGroup.response(request); + assertThat(response.getError()).isEqualTo(JsonRpcError.FIND_ON_CHAIN_PRIVACY_GROUP_ERROR); + verify(privacyController).findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY); + } + + @Test + public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() { + when(privacyController.findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + .thenThrow(new MultiTenancyValidationException("validation failed")); + + final JsonRpcResponse expectedResponse = + new JsonRpcErrorResponse( + request.getRequest().getId(), JsonRpcError.FIND_ON_CHAIN_PRIVACY_GROUP_ERROR); + final JsonRpcResponse response = privxFindOnChainPrivacyGroup.response(request); + assertThat(response).isEqualTo(expectedResponse); + verify(privacyController).findOnChainPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java index cb22bb2794..7492862613 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.transaction.CallParameter; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.tuweni.bytes.Bytes; @@ -165,8 +166,16 @@ public class MultiTenancyPrivacyController implements PrivacyController { @Override public List findOnChainPrivacyGroup( - final List asList, final String enclaveKey) { - return privacyController.findOnChainPrivacyGroup(asList, enclaveKey); + final List addresses, final String enclavePublicKey) { + if (!addresses.contains(enclavePublicKey)) { + throw new MultiTenancyValidationException( + "Privacy group addresses must contain the enclave public key"); + } + List resultantGroups = + privacyController.findOnChainPrivacyGroup(addresses, enclavePublicKey); + return resultantGroups.stream() + .filter(g -> g.getMembers().contains(enclavePublicKey)) + .collect(Collectors.toList()); } @Override diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionDataFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionDataFixture.java index a9d155074d..87ae3be90a 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionDataFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionDataFixture.java @@ -47,8 +47,8 @@ public class PrivateTransactionDataFixture { new BigInteger( "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); - public static final Bytes VALID_BASE64_ENCLAVE_KEY = - Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + public static final Bytes32 VALID_BASE64_ENCLAVE_KEY = + Bytes32.wrap(Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=")); public static final Bytes VALID_CONTRACT_DEPLOYMENT_PAYLOAD = Bytes.fromHexString( @@ -139,6 +139,24 @@ public class PrivateTransactionDataFixture { null); } + public static ReceiveResponse generateAddToGroupReceiveResponse( + final PrivateTransaction privateTransaction, final Transaction markerTransaction) { + final List privateTransactionWithMetadataList = + generateAddBlobResponse(privateTransaction, markerTransaction); + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + privateTransactionWithMetadataList.stream() + .forEach( + privateTransactionWithMetadata -> privateTransactionWithMetadata.writeTo(rlpOutput)); + rlpOutput.endList(); + return new ReceiveResponse( + rlpOutput.encoded().toBase64String().getBytes(UTF_8), + privateTransaction.getPrivacyGroupId().isPresent() + ? privateTransaction.getPrivacyGroupId().get().toBase64String() + : "", + null); + } + public static List generateAddBlobResponse( final PrivateTransaction privateTransaction, final Transaction markerTransaction) { final PrivateTransactionWithMetadata privateTransactionWithMetadata = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index 81d61f5711..97727576b6 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -15,23 +15,39 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.VALID_BASE64_ENCLAVE_KEY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.TransactionLocation; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.DefaultEvmAccount; import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.MutableAccount; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.core.WorldUpdater; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.Collections; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.Before; import org.junit.Test; @@ -44,6 +60,7 @@ public class PrivacyBlockProcessorTest { private WorldStateArchive privateWorldStateArchive; private Enclave enclave; private ProtocolSchedule protocolSchedule; + private WorldStateArchive publicWorldStateArchive; @Before public void setUp() { @@ -59,6 +76,8 @@ public class PrivacyBlockProcessorTest { enclave, privateStateStorage, privateWorldStateArchive); + publicWorldStateArchive = mock(WorldStateArchive.class); + privacyBlockProcessor.setPublicWorldStateArchive(publicWorldStateArchive); } @Test @@ -87,5 +106,108 @@ public class PrivacyBlockProcessorTest { firstBlock.getHeader(), firstBlock.getBody().getTransactions(), firstBlock.getBody().getOmmers()); + verify(blockProcessor) + .processBlock( + blockchain, + mutableWorldState, + secondBlock.getHeader(), + secondBlock.getBody().getTransactions(), + secondBlock.getBody().getOmmers()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Test + public void mustPerformRehydration() { + final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + final Blockchain blockchain = mock(Blockchain.class); + final MutableWorldState mutableWorldState = mock(MutableWorldState.class); + when(mutableWorldState.updater()).thenReturn(mock(WorldUpdater.class)); + + final Block firstBlock = + blockDataGenerator.block( + BlockDataGenerator.BlockOptions.create() + .addTransaction(PrivateTransactionDataFixture.privacyMarkerTransactionOnChain())); + final Block secondBlock = + blockDataGenerator.block( + BlockDataGenerator.BlockOptions.create() + .addTransaction( + PrivateTransactionDataFixture.privacyMarkerTransactionOnChainAdd())); + + when(enclave.receive(any())) + .thenReturn( + PrivateTransactionDataFixture.generateAddToGroupReceiveResponse( + PrivateTransactionDataFixture.privateTransactionBesu(), + PrivateTransactionDataFixture.privacyMarkerTransactionOnChain())); + when(blockchain.getTransactionLocation(any())) + .thenReturn(Optional.of(new TransactionLocation(firstBlock.getHash(), 0))); + when(blockchain.getBlockByHash(any())).thenReturn(Optional.of(firstBlock)); + when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(firstBlock.getHeader())); + final ProtocolSpec protocolSpec = mockProtocolSpec(); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(protocolSpec); + when(publicWorldStateArchive.getMutable(any())).thenReturn(Optional.of(mutableWorldState)); + final MutableWorldState mockPrivateStateArchive = mockPrivateStateArchive(); + when(privateWorldStateArchive.getMutable(any())) + .thenReturn(Optional.of(mockPrivateStateArchive)); + + final PrivacyGroupHeadBlockMap expected = + new PrivacyGroupHeadBlockMap( + Collections.singletonMap(VALID_BASE64_ENCLAVE_KEY, firstBlock.getHash())); + privateStateStorage + .updater() + .putPrivacyGroupHeadBlockMap(firstBlock.getHash(), expected) + .putPrivateBlockMetadata( + firstBlock.getHash(), VALID_BASE64_ENCLAVE_KEY, PrivateBlockMetadata.empty()) + .commit(); + + privacyBlockProcessor.processBlock(blockchain, mutableWorldState, secondBlock); + verify(blockProcessor) + .processBlock( + blockchain, + mutableWorldState, + secondBlock.getHeader(), + secondBlock.getBody().getTransactions(), + secondBlock.getBody().getOmmers()); + } + + private MutableWorldState mockPrivateStateArchive() { + final MutableWorldState mockPrivateState = mock(MutableWorldState.class); + final WorldUpdater mockWorldUpdater = mock(WorldUpdater.class); + final DefaultEvmAccount mockDefaultEvmAccount = mock(DefaultEvmAccount.class); + final MutableAccount mockMutableAccount = mock(MutableAccount.class); + when(mockDefaultEvmAccount.getMutable()).thenReturn(mockMutableAccount); + when(mockWorldUpdater.createAccount(any())).thenReturn(mockDefaultEvmAccount); + when(mockPrivateState.updater()).thenReturn(mockWorldUpdater); + when(mockPrivateState.rootHash()).thenReturn(Hash.ZERO); + return mockPrivateState; + } + + @SuppressWarnings("rawtypes") + private ProtocolSpec mockProtocolSpec() { + final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + final TransactionProcessor mockPublicTransactionProcessor = mock(TransactionProcessor.class); + when(mockPublicTransactionProcessor.processTransaction( + any(), any(), any(), any(), any(), any(), anyBoolean(), any())) + .thenReturn( + MainnetTransactionProcessor.Result.successful( + Collections.emptyList(), 0, Bytes.EMPTY, ValidationResult.valid())); + when(protocolSpec.getTransactionProcessor()).thenReturn(mockPublicTransactionProcessor); + final PrivateTransactionProcessor mockPrivateTransactionProcessor = + mock(PrivateTransactionProcessor.class); + when(mockPrivateTransactionProcessor.processTransaction( + any(), any(), any(), any(), any(), any(), any(), any(), any())) + .thenReturn( + PrivateTransactionProcessor.Result.successful( + Collections.emptyList(), 0, Bytes.EMPTY, ValidationResult.valid())); + when(protocolSpec.getPrivateTransactionProcessor()).thenReturn(mockPrivateTransactionProcessor); + final AbstractBlockProcessor.TransactionReceiptFactory mockTransactionReceiptFactory = + mock(AbstractBlockProcessor.TransactionReceiptFactory.class); + when(mockTransactionReceiptFactory.create(any(), any(), anyLong())) + .thenReturn(new TransactionReceipt(0, 0, Collections.emptyList(), Optional.empty())); + when(protocolSpec.getTransactionReceiptFactory()).thenReturn(mockTransactionReceiptFactory); + when(protocolSpec.getBlockReward()).thenReturn(Wei.ZERO); + when(protocolSpec.getMiningBeneficiaryCalculator()) + .thenReturn(mock(MiningBeneficiaryCalculator.class)); + when(protocolSpec.isSkipZeroBlockRewards()).thenReturn(true); + return protocolSpec; } }