From 4d72369469f848b744c34481de352fc1156b2e2b Mon Sep 17 00:00:00 2001 From: Stefan Pingel <16143240+pinges@users.noreply.github.com> Date: Thu, 11 Nov 2021 13:29:46 +1000 Subject: [PATCH] Refactor privacy controller (#2971) * refactor of PrivacyController Signed-off-by: Stefan Pingel --- .../ExpectValidPrivateTransactionReceipt.java | 49 +- .../condition/PrivateTransactionVerifier.java | 8 + .../privacy/PrivacyClusterAcceptanceTest.java | 18 +- .../privacy/PrivateGenesisAcceptanceTest.java | 3 +- .../MultiTenancyAcceptanceTest.java | 106 ++++- ...tiTenancyValidationFailAcceptanceTest.java | 4 +- .../OnchainMultiTenancyAcceptanceTest.java | 21 +- .../hyperledger/besu/PrivacyReorgTest.java | 2 +- .../besu/enclave/types/PrivacyGroup.java | 4 + .../privacy/methods/PrivGetFilterChanges.java | 6 +- .../privacy/methods/PrivGetFilterLogs.java | 5 +- .../privacy/methods/PrivUninstallFilter.java | 5 +- ...strictedOffchainEeaSendRawTransaction.java | 9 +- ...estrictedOnchainEeaSendRawTransaction.java | 60 +-- .../privacy/methods/priv/PrivCall.java | 3 - .../methods/priv/PrivDebugGetStateRoot.java | 13 + .../priv/PrivDistributeRawTransaction.java | 34 +- .../methods/priv/PrivFindPrivacyGroup.java | 2 +- .../priv/PrivGetEeaTransactionCount.java | 32 +- .../privacy/methods/priv/PrivGetLogs.java | 37 +- .../methods/priv/PrivGetTransactionCount.java | 2 +- .../privacy/methods/priv/PrivNewFilter.java | 8 +- .../privx/PrivxFindOnchainPrivacyGroup.java | 9 +- .../PrivacyApiGroupJsonRpcMethods.java | 40 +- .../AbstractPrivateSubscriptionMethod.java | 2 +- .../websocket/methods/PrivSubscribe.java | 8 +- .../websocket/methods/PrivUnsubscribe.java | 7 +- .../PrivateWebSocketMethodsFactory.java | 39 +- ...ctedOffchainEeaSendRawTransactionTest.java | 4 +- ...ictedOnchainEeaSendRawTransactionTest.java | 6 +- .../privacy/methods/priv/PrivCallTest.java | 16 - .../priv/PrivDebugGetStateRootTest.java | 2 - .../priv/PrivFindPrivacyGroupTest.java | 12 +- .../priv/PrivGetEeaTransactionCountTest.java | 22 +- .../priv/PrivGetFilterChangesTest.java | 38 +- .../methods/priv/PrivGetFilterLogsTest.java | 30 -- .../privacy/methods/priv/PrivGetLogsTest.java | 34 -- .../priv/PrivGetTransactionCountTest.java | 9 +- .../methods/priv/PrivNewFilterTest.java | 21 - .../methods/priv/PrivUninstallFilterTest.java | 31 -- .../PrivxFindOnchainPrivacyGroupTest.java | 15 +- .../PrivacyApiGroupJsonRpcMethodsTest.java | 4 +- .../websocket/methods/PrivSubscribeTest.java | 30 -- .../methods/PrivUnsubscribeTest.java | 34 -- .../privacy/AbstractPrivacyController.java | 122 +++++ .../AbstractRestrictedPrivacyController.java | 56 +++ .../ChainHeadPrivateNonceProvider.java | 2 +- .../MultiTenancyPrivacyController.java | 165 +++++++ .../privacy/OnchainPrivacyController.java | 364 +++++++++++++++ .../besu/ethereum/privacy/OnchainUtil.java | 50 +++ .../privacy/PluginPrivacyController.java | 167 +------ .../ethereum/privacy/PrivacyController.java | 33 +- .../ethereum/privacy/PrivacyGroupUtil.java | 31 +- .../privacy/PrivateTransactionProcessor.java | 3 +- .../RestrictedDefaultPrivacyController.java | 420 ++---------------- ...strictedMultiTenancyPrivacyController.java | 326 -------------- ...iTenancyPrivacyControllerOnchainTest.java} | 31 +- ...=> MultiTenancyPrivacyControllerTest.java} | 194 ++------ .../privacy/OnchainPrivacyControllerTest.java | 408 +++++++++++++++++ ...estrictedDefaultPrivacyControllerTest.java | 134 +----- 60 files changed, 1704 insertions(+), 1646 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyController.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainUtil.java delete mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyController.java rename ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/{RestrictedMultiTenancyPrivacyControllerOnchainTest.java => MultiTenancyPrivacyControllerOnchainTest.java} (74%) rename ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/{RestrictedMultiTenancyPrivacyControllerTest.java => MultiTenancyPrivacyControllerTest.java} (61%) create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyControllerTest.java diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java index 0c0cf98de..ff8e0c2c5 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java @@ -25,31 +25,60 @@ public class ExpectValidPrivateTransactionReceipt implements PrivateCondition { private final PrivacyTransactions transactions; private final String transactionHash; private final PrivateTransactionReceipt expectedReceipt; + private final boolean ignoreOutput; public ExpectValidPrivateTransactionReceipt( final PrivacyTransactions transactions, final String transactionHash, final PrivateTransactionReceipt expectedReceipt) { + this(transactions, transactionHash, expectedReceipt, false); + } + + public ExpectValidPrivateTransactionReceipt( + final PrivacyTransactions transactions, + final String transactionHash, + final PrivateTransactionReceipt expectedReceipt, + final boolean ignoreOutput) { this.transactions = transactions; this.transactionHash = transactionHash; this.expectedReceipt = expectedReceipt; + this.ignoreOutput = ignoreOutput; } @Override public void verify(final PrivacyNode node) { final PrivateTransactionReceipt actualReceipt = node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); - assertThat(actualReceipt) - .usingRecursiveComparison() - .ignoringFields( - "commitmentHash", "logs", "blockHash", "blockNumber", "logsBloom", "transactionIndex") - // TODO: The fields blockHash, blockNumber, logsBloom and - // transactionIndex have to be ignored as the class - // org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt does not contain these - // fields. Once web3j has been updated these ignores can be removed. - .isEqualTo(expectedReceipt); - + if (ignoreOutput) { + // output can be ignored if it is checked separately + assertThat(actualReceipt) + .usingRecursiveComparison() + .ignoringFields( + "commitmentHash", + "logs", + "blockHash", + "blockNumber", + "logsBloom", + "transactionIndex", + "output") + // TODO: The fields blockHash, blockNumber, logsBloom, transactionIndex and output have to + // be ignored as + // the class org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt does not + // contain + // these fields. Once web3j has been updated these ignores can be removed. + .isEqualTo(expectedReceipt); + } else { + assertThat(actualReceipt) + .usingRecursiveComparison() + .ignoringFields( + "commitmentHash", "logs", "blockHash", "blockNumber", "logsBloom", "transactionIndex") + // TODO: The fields blockHash, blockNumber, logsBloom and transactionIndex have to be + // ignored as the class + // org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt does not contain + // these fields. Once web3j has been updated these ignores can be removed. + .isEqualTo(expectedReceipt); + } assertThat(actualReceipt.getLogs().size()).isEqualTo(expectedReceipt.getLogs().size()); for (int i = 0; i < expectedReceipt.getLogs().size(); i++) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java index 6dd2c7abb..ad4d02370 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java @@ -39,6 +39,14 @@ public class PrivateTransactionVerifier { return new ExpectValidPrivateTransactionReceipt(transactions, transactionHash, receipt); } + public ExpectValidPrivateTransactionReceipt validPrivateTransactionReceipt( + final String transactionHash, + final PrivateTransactionReceipt receipt, + final boolean ignoreOutput) { + return new ExpectValidPrivateTransactionReceipt( + transactions, transactionHash, receipt, ignoreOutput); + } + public ExpectExistingPrivateTransactionReceipt existingPrivateTransactionReceipt( final String transactionHash) { return new ExpectExistingPrivateTransactionReceipt(transactions, transactionHash); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java index 24d2c01ba..847006300 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java @@ -55,9 +55,6 @@ import org.web3j.utils.Numeric; @RunWith(Parameterized.class) public class PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { - private static final String eventEmitterDeployed = - "0x608060405234801561001057600080fd5b506004361061005d577c010000000000000000000000000000000000000000000000000000000060003504633fa4f24581146100625780636057361d1461007c57806367e404ce1461009b575b600080fd5b61006a6100cc565b60408051918252519081900360200190f35b6100996004803603602081101561009257600080fd5b50356100d2565b005b6100a3610131565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60025490565b604080513381526020810183905281517fc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5929181900390910190a16002556001805473ffffffffffffffffffffffffffffffffffffffff191633179055565b60015473ffffffffffffffffffffffffffffffffffffffff169056fea265627a7a7231582090b93fa1c20946b6f8b2ad11f1b2c0aa357217287877d3d1cfeef69bd7f4788564736f6c63430005110032"; - private final PrivacyNode alice; private final PrivacyNode bob; private final PrivacyNode charlie; @@ -220,7 +217,7 @@ public class PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { contractAddress, "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", null, - eventEmitterDeployed, + null, // ignored in the following call, checked separately below Collections.emptyList(), "0x023955c49d6265c579561940287449242704d5fd239ff07ea36a3fc7aface61c", "0x82e521ee16ff13104c5f81e8354ecaaafd5450b710b07f620204032bfe76041a", @@ -233,11 +230,20 @@ public class PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { alice.verify( privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt)); + transactionHash, expectedReceipt, true)); + + final PrivateTransactionReceipt alicePrivateTransactionReceipt = + alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); + assertThat(EventEmitter.BINARY) + .contains(alicePrivateTransactionReceipt.getOutput().substring(2)); bob.verify( privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt)); + transactionHash, expectedReceipt, true)); + + final PrivateTransactionReceipt bobPrivateTransactionReceipt = + bob.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); + assertThat(EventEmitter.BINARY).contains(bobPrivateTransactionReceipt.getOutput().substring(2)); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java index 7c5193387..531e00645 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java @@ -71,8 +71,7 @@ public class PrivateGenesisAcceptanceTest extends ParameterizedEnclaveTestBase { alice.getEnclaveKey(), privacyGroupId)); - privateTransactionVerifier.existingPrivateTransactionReceipt( - eventEmitter.store(BigInteger.valueOf(42)).send().getTransactionHash()); + eventEmitter.store(BigInteger.valueOf(42)).send(); final EthCall response = alice.execute( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java index 4e78af91e..fb5f1caa7 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.plugin.data.Restriction; @@ -42,7 +43,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurati import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -50,7 +53,8 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.io.Base64; +import org.apache.tuweni.bytes.Bytes32; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -75,12 +79,15 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { private static final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private static final String PARTICIPANT_ENCLAVE_KEY0 = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final Bytes LEAGCY_PRIVATE_FROM = Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY0); private static final String PARTICIPANT_ENCLAVE_KEY1 = "sgFkVOyFndZe/5SAZJO5UYbrl7pezHetveriBBWWnE8="; + private static final List LEGACY_PRIVATE_FOR = + List.of(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY1)); private static final String PARTICIPANT_ENCLAVE_KEY2 = "R1kW75NQC9XX3kwNpyPjCBFflM29+XvnKKS9VLrUkzo="; private static final String PARTICIPANT_ENCLAVE_KEY3 = - "QzHuACXpfhoGAgrQriWJcDJ6MrUwcCvutKMoAn9KplQ="; + "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private final Address senderAddress = Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); @@ -237,29 +244,37 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { public void privGetEeaTransactionCountSuccessShouldReturnExpectedTransactionCount() throws JsonProcessingException { final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress); + getValidLegacySignedPrivateTransaction(senderAddress); final String accountAddress = validSignedPrivateTransaction.getSender().toHexString(); - final String senderAddressBase64 = Base64.encode(Bytes.wrap(accountAddress.getBytes(UTF_8))); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); - final List groupMembership = - List.of(testPrivacyGroup(emptyList(), PrivacyGroup.Type.LEGACY)); + final String privateTxRlp = getRLPOutput(validSignedPrivateTransaction).encoded().toHexString(); - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - receiveEnclaveStub(validSignedPrivateTransaction); - findPrivacyGroupEnclaveStub(groupMembership); + retrieveEeaPrivacyGroupEnclaveStub(validSignedPrivateTransaction); + sendEnclaveStub( + Bytes32.ZERO.toBase64String()); // can be any value, as we are stubbing the enclave + receiveEnclaveStubEea(validSignedPrivateTransaction); - node.verify(priv.getTransactionCount(accountAddress, PRIVACY_GROUP_ID, 0)); - final Hash transactionHash = - node.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); + final String privateFrom = validSignedPrivateTransaction.getPrivateFrom().toBase64String(); + final String[] privateFor = + validSignedPrivateTransaction.getPrivateFor().orElseThrow().stream() + .map(pf -> pf.toBase64String()) + .toArray(String[]::new); + node.verify(priv.getEeaTransactionCount(accountAddress, privateFrom, privateFor, 0)); + + final Hash transactionHash = node.execute(privacyTransactions.sendRawTransaction(privateTxRlp)); node.verify(priv.getSuccessfulTransactionReceipt(transactionHash)); - final String privateFrom = PARTICIPANT_ENCLAVE_KEY0; - final String[] privateFor = {senderAddressBase64}; node.verify(priv.getEeaTransactionCount(accountAddress, privateFrom, privateFor, 1)); } + @NotNull + private Bytes32 getPrivacyGroupIdFromEeaTransaction( + final PrivateTransaction validSignedPrivateTransaction) { + return PrivacyGroupUtil.calculateEeaPrivacyGroupId( + validSignedPrivateTransaction.getPrivateFrom(), + validSignedPrivateTransaction.getPrivateFor().get()); + } + private void findPrivacyGroupEnclaveStub(final List groupMembership) throws JsonProcessingException { final String findGroupResponse = mapper.writeValueAsString(groupMembership); @@ -280,7 +295,22 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { private void retrievePrivacyGroupEnclaveStub() throws JsonProcessingException { final String retrieveGroupResponse = mapper.writeValueAsString( - testPrivacyGroup(List.of(PARTICIPANT_ENCLAVE_KEY0), PrivacyGroup.Type.PANTHEON)); + testPrivacyGroup( + List.of(PARTICIPANT_ENCLAVE_KEY0, PARTICIPANT_ENCLAVE_KEY1), + PrivacyGroup.Type.PANTHEON)); + stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); + } + + private void retrieveEeaPrivacyGroupEnclaveStub(final PrivateTransaction tx) + throws JsonProcessingException { + final ArrayList members = new ArrayList<>(); + members.add(tx.getPrivateFrom().toBase64String()); + members.addAll( + tx.getPrivateFor().orElseThrow().stream() + .map(pf -> pf.toBase64String()) + .collect(Collectors.toList())); + final String retrieveGroupResponse = + mapper.writeValueAsString(testPrivacyGroupEea(members, PrivacyGroup.Type.LEGACY)); stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); } @@ -299,6 +329,19 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { stubFor(post("/receive").willReturn(ok(receiveResponse))); } + private void receiveEnclaveStubEea(final PrivateTransaction privTx) + throws JsonProcessingException { + final BytesValueRLPOutput rlpOutput = getRLPOutputForReceiveResponse(privTx); + final String senderKey = privTx.getPrivateFrom().toBase64String(); + final String receiveResponse = + mapper.writeValueAsString( + new ReceiveResponse( + rlpOutput.encoded().toBase64String().getBytes(UTF_8), + getPrivacyGroupIdFromEeaTransaction(privTx).toBase64String(), + senderKey)); + stubFor(post("/receive").willReturn(ok(receiveResponse))); + } + private BytesValueRLPOutput getRLPOutputForReceiveResponse( final PrivateTransaction privateTransaction) { final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); @@ -317,6 +360,18 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { return new PrivacyGroup(PRIVACY_GROUP_ID, groupType, "test", "testGroup", groupMembers); } + private PrivacyGroup testPrivacyGroupEea( + final List groupMembers, final PrivacyGroup.Type groupType) { + final Bytes32 privacyGroupId = + PrivacyGroupUtil.calculateEeaPrivacyGroupId( + Bytes.fromBase64String(groupMembers.get(0)), + groupMembers.stream() + .map(gm -> Bytes.fromBase64String(gm)) + .collect(Collectors.toList())); + return new PrivacyGroup( + privacyGroupId.toBase64String(), groupType, "test", "testGroup", groupMembers); + } + private static PrivateTransaction getValidSignedPrivateTransaction(final Address senderAddress) { return PrivateTransaction.builder() .nonce(0) @@ -332,4 +387,21 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) .signAndBuild(TEST_KEY); } + + private static PrivateTransaction getValidLegacySignedPrivateTransaction( + final Address senderAddress) { + return PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.ZERO) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload(Bytes.wrap(new byte[] {})) + .sender(senderAddress) + .chainId(BigInteger.valueOf(1337)) + .privateFrom(LEAGCY_PRIVATE_FROM) + .privateFor(LEGACY_PRIVATE_FOR) + .restriction(Restriction.RESTRICTED) + .signAndBuild(TEST_KEY); + } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java index bc404019e..515248347 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java @@ -172,7 +172,9 @@ public class MultiTenancyValidationFailAcceptanceTest extends AcceptanceTestBase final Transaction transaction = privacyTransactions.getEeaTransactionCount( accountAddress, OTHER_ENCLAVE_PUBLIC_KEY, privateFor); - node.verify(priv.multiTenancyValidationFail(transaction, GET_PRIVATE_TRANSACTION_NONCE_ERROR)); + node.verify( + priv.multiTenancyValidationFail( + transaction, PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY)); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/OnchainMultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/OnchainMultiTenancyAcceptanceTest.java index ff0cc7072..ee2c478fd 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/OnchainMultiTenancyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/OnchainMultiTenancyAcceptanceTest.java @@ -65,9 +65,6 @@ public class OnchainMultiTenancyAcceptanceTest extends OnchainPrivacyAcceptanceT .collect(Collectors.toList()); } - private static final String eventEmitterDeployed = - "0x608060405234801561001057600080fd5b506004361061005d577c010000000000000000000000000000000000000000000000000000000060003504633fa4f24581146100625780636057361d1461007c57806367e404ce1461009b575b600080fd5b61006a6100cc565b60408051918252519081900360200190f35b6100996004803603602081101561009257600080fd5b50356100d2565b005b6100a3610131565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60025490565b604080513381526020810183905281517fc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5929181900390910190a16002556001805473ffffffffffffffffffffffffffffffffffffffff191633179055565b60015473ffffffffffffffffffffffffffffffffffffffff169056fea265627a7a7231582090b93fa1c20946b6f8b2ad11f1b2c0aa357217287877d3d1cfeef69bd7f4788564736f6c63430005110032"; - private static final PermissioningTransactions permissioningTransactions = new PermissioningTransactions(); private static final long VALUE_SET = 10L; @@ -152,15 +149,15 @@ public class OnchainMultiTenancyAcceptanceTest extends OnchainPrivacyAcceptanceT privateTransactionVerifier.validPrivateTransactionReceipt( transactionHash, (PrivateTransactionReceipt) eventEmitter.getTransactionReceipt().get())); - assertThat( - privacyNode - .execute( - privacyTransactions.privGetCode( - privacyGroupId, - Address.fromHexString(eventEmitter.getContractAddress()), - "latest")) - .toHexString()) - .isEqualTo(eventEmitterDeployed); + final String actual = + privacyNode + .execute( + privacyTransactions.privGetCode( + privacyGroupId, + Address.fromHexString(eventEmitter.getContractAddress()), + "latest")) + .toHexString(); + assertThat(EventEmitter.BINARY).contains(actual.substring(2)); // check that getting the transaction receipt does not work if you are not a member privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java index 6ad170c60..a2055503f 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java @@ -163,7 +163,7 @@ public class PrivacyReorgTest { .build(); privacyParameters.setPrivacyUserId(ENCLAVE_PUBLIC_KEY.toBase64String()); privacyController = mock(RestrictedDefaultPrivacyController.class); - when(privacyController.findOffchainPrivacyGroupByGroupId(any(), any())) + when(privacyController.findPrivacyGroupByGroupId(any(), any())) .thenReturn(Optional.of(new PrivacyGroup())); privateStateRootResolver = diff --git a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java index aa6e41aca..bf4517b4b 100644 --- a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java +++ b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java @@ -65,6 +65,10 @@ public class PrivacyGroup implements Serializable { this.members = members; } + public void addMembers(final List participantsFromParameter) { + members.addAll(participantsFromParameter); + } + public PrivacyGroup() {} public PrivacyGroup( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java index a93b6412a..cffa17d12 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogsResult; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.List; @@ -53,8 +54,9 @@ public class PrivGetFilterChanges implements JsonRpcMethod { final String privacyGroupId = requestContext.getRequiredParameter(0, String.class); final String filterId = requestContext.getRequiredParameter(1, String.class); - checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(requestContext, privacyGroupId); - + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(requestContext, privacyGroupId); + } final List logs = filterManager.logsChanges(filterId); if (logs != null) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), new LogsResult(logs)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java index 2424c8c8c..768cd29f6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogsResult; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.List; @@ -53,7 +54,9 @@ public class PrivGetFilterLogs implements JsonRpcMethod { final String privacyGroupId = request.getRequiredParameter(0, String.class); final String filterId = request.getRequiredParameter(1, String.class); - checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(request, privacyGroupId); + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(request, privacyGroupId); + } final List logs = filterManager.logs(filterId); if (logs != null) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java index feb055dc9..1683c9511 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; 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.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; public class PrivUninstallFilter implements JsonRpcMethod { @@ -47,7 +48,9 @@ public class PrivUninstallFilter implements JsonRpcMethod { final String privacyGroupId = request.getRequiredParameter(0, String.class); final String filterId = request.getRequiredParameter(1, String.class); - checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(request, privacyGroupId); + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(request, privacyGroupId); + } return new JsonRpcSuccessResponse( request.getRequest().getId(), filterManager.uninstallFilter(filterId)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransaction.java index 49210cc50..4f7754cd6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransaction.java @@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.eea; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY; import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; -import static org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil.findOffchainPrivacyGroup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.enclave.types.PrivacyGroup; @@ -79,8 +78,12 @@ public class RestrictedOffchainEeaSendRawTransaction extends AbstractEeaSendRawT final String privacyUserId = privacyIdProvider.getPrivacyUserId(user); final Optional maybePrivacyGroup = - findOffchainPrivacyGroup( - privacyController, privateTransaction.getPrivacyGroupId(), privacyUserId); + privateTransaction + .getPrivacyGroupId() + .flatMap( + privacyGroupId -> + privacyController.findPrivacyGroupByGroupId( + privacyGroupId.toBase64String(), privacyUserId)); final String privateTransactionLookupId = privacyController.createPrivateMarkerTransactionPayload( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransaction.java index c365c4b68..1b45c3946 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransaction.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.eea; import static org.hyperledger.besu.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY; -import static org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil.findOnchainPrivacyGroup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.enclave.types.PrivacyGroup; @@ -24,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.privacy.OnchainUtil; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; @@ -31,11 +31,11 @@ import org.hyperledger.besu.ethereum.util.NonceProvider; import org.hyperledger.besu.plugin.data.Restriction; import org.hyperledger.besu.plugin.services.privacy.PrivateMarkerTransactionFactory; +import java.util.List; import java.util.Optional; import io.vertx.ext.auth.User; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; public class RestrictedOnchainEeaSendRawTransaction extends AbstractEeaSendRawTransaction { @@ -69,34 +69,47 @@ public class RestrictedOnchainEeaSendRawTransaction extends AbstractEeaSendRawTr final Address sender, final PrivateTransaction privateTransaction, final Optional user) { - if (privateTransaction.getPrivacyGroupId().isEmpty()) { + final Optional maybePrivacyGroupId = privateTransaction.getPrivacyGroupId(); + if (maybePrivacyGroupId.isEmpty()) { throw new JsonRpcErrorResponseException(JsonRpcError.ONCHAIN_PRIVACY_GROUP_ID_NOT_AVAILABLE); } + final Bytes privacyGroupId = maybePrivacyGroupId.get(); final String privacyUserId = privacyIdProvider.getPrivacyUserId(user); - final Optional privacyGroup = - findOnchainPrivacyGroup( - privacyController, - privateTransaction.getPrivacyGroupId(), - privacyUserId, - privateTransaction); + Optional maybePrivacyGroup = + privacyController.findPrivacyGroupByGroupId(privacyGroupId.toBase64String(), privacyUserId); - if (privacyGroup.isEmpty()) { + final boolean isGroupAdditionTransaction = + OnchainUtil.isGroupAdditionTransaction(privateTransaction); + if (maybePrivacyGroup.isEmpty() && !isGroupAdditionTransaction) { throw new JsonRpcErrorResponseException(JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); } - final Bytes privacyGroupId = privateTransaction.getPrivacyGroupId().get(); + if (isGroupAdditionTransaction) { + final List participantsFromParameter = + OnchainUtil.getParticipantsFromParameter(privateTransaction.getPayload()); + if (maybePrivacyGroup.isEmpty()) { + maybePrivacyGroup = + Optional.of( + new PrivacyGroup( + privacyGroupId.toBase64String(), + PrivacyGroup.Type.ONCHAIN, + null, + null, + participantsFromParameter)); + } else { + maybePrivacyGroup.get().addMembers(participantsFromParameter); + } + } - final String privateTransactionLookupId = - privacyController.createPrivateMarkerTransactionPayload( - privateTransaction, privacyUserId, privacyGroup); - final Optional addPayloadPrivateTransactionLookupId = - privacyController.buildAndSendAddPayload( - privateTransaction, Bytes32.wrap(privacyGroupId), privacyUserId); + if (!maybePrivacyGroup.get().getMembers().contains(privacyUserId)) { + throw new JsonRpcErrorResponseException(JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); + } final String pmtPayload = - buildCompoundLookupId(privateTransactionLookupId, addPayloadPrivateTransactionLookupId); + privacyController.createPrivateMarkerTransactionPayload( + privateTransaction, privacyUserId, maybePrivacyGroup); return createPrivateMarkerTransaction( sender, ONCHAIN_PRIVACY, pmtPayload, privateTransaction, privacyUserId); @@ -106,15 +119,4 @@ public class RestrictedOnchainEeaSendRawTransaction extends AbstractEeaSendRawTr protected long getGasLimit(final PrivateTransaction privateTransaction, final String pmtPayload) { return privateTransaction.getGasLimit(); } - - private String buildCompoundLookupId( - final String privateTransactionLookupId, - final Optional maybePrivateTransactionLookupId) { - return maybePrivateTransactionLookupId.isPresent() - ? Bytes.concatenate( - Bytes.fromBase64String(privateTransactionLookupId), - Bytes.fromBase64String(maybePrivateTransactionLookupId.get())) - .toBase64String() - : privateTransactionLookupId; - } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java index f2c6b8768..8243e3cc0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java @@ -60,9 +60,6 @@ public class PrivCall extends AbstractBlockParameterMethod { final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); - PrivUtil.checkMembershipForAuthenticatedUser( - privacyController, privacyIdProvider, request, privacyGroupId, blockNumber); - return privacyController .simulatePrivateTransaction(privacyGroupId, privacyUserId, callParams, blockNumber) .map( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java index 1455c8042..96b1d112a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static org.apache.logging.log4j.LogManager.getLogger; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.FIND_PRIVACY_GROUP_ERROR; +import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -32,6 +33,7 @@ import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Optional; +import java.util.regex.Pattern; import org.apache.logging.log4j.Logger; @@ -74,6 +76,17 @@ public class PrivDebugGetStateRoot extends AbstractBlockParameterMethod { } catch (final MultiTenancyValidationException e) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), FIND_PRIVACY_GROUP_ERROR); + } catch (final EnclaveClientException e) { + final Pattern pattern = Pattern.compile("^Privacy group.*not found$"); + if (e.getMessage().equals(JsonRpcError.ENCLAVE_PRIVACY_GROUP_MISSING.getMessage()) + || pattern.matcher(e.getMessage()).find()) { + LOG.error("Failed to retrieve privacy group"); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), FIND_PRIVACY_GROUP_ERROR); + } else { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR); + } } catch (final Exception e) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS); 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 2970272b1..5d1de4923 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 @@ -20,8 +20,6 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter.co import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.ENCLAVE_ERROR; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY; -import static org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil.findOffchainPrivacyGroup; -import static org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil.findOnchainPrivacyGroup; import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -34,6 +32,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; +import org.hyperledger.besu.ethereum.privacy.OnchainUtil; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -41,6 +40,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import java.util.Base64; +import java.util.List; import java.util.Optional; import org.apache.logging.log4j.Logger; @@ -88,14 +88,30 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod { return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_ID_NOT_AVAILABLE); } - final Optional maybePrivacyGroup = - onchainPrivacyGroupsEnabled - ? findOnchainPrivacyGroup( - privacyController, maybePrivacyGroupId, privacyUserId, privateTransaction) - : findOffchainPrivacyGroup(privacyController, maybePrivacyGroupId, privacyUserId); + Optional maybePrivacyGroup = + maybePrivacyGroupId.flatMap( + gId -> + privacyController.findPrivacyGroupByGroupId(gId.toBase64String(), privacyUserId)); - if (onchainPrivacyGroupsEnabled && maybePrivacyGroup.isEmpty()) { - return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); + if (onchainPrivacyGroupsEnabled) { + if (OnchainUtil.isGroupAdditionTransaction(privateTransaction)) { + final List participantsFromParameter = + OnchainUtil.getParticipantsFromParameter(privateTransaction.getPayload()); + if (maybePrivacyGroup.isEmpty()) { + maybePrivacyGroup = + Optional.of( + new PrivacyGroup( + maybePrivacyGroupId.get().toBase64String(), + PrivacyGroup.Type.ONCHAIN, + "", + "", + participantsFromParameter)); + } + maybePrivacyGroup.get().addMembers(participantsFromParameter); + } + if (maybePrivacyGroup.isEmpty()) { + return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST); + } } final ValidationResult validationResult = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java index 513692a0e..bbd125f81 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java @@ -62,7 +62,7 @@ public class PrivFindPrivacyGroup implements JsonRpcMethod { try { response = Arrays.asList( - privacyController.findOffchainPrivacyGroupByMembers( + privacyController.findPrivacyGroupByMembers( Arrays.asList(addresses), privacyIdProvider.getPrivacyUserId(requestContext.getUser()))); } catch (final MultiTenancyValidationException e) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java index db58589c8..80e9a759b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static org.apache.logging.log4j.LogManager.getLogger; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.GET_PRIVATE_TRANSACTION_NONCE_ERROR; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -29,8 +30,15 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSucces import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; +import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class PrivGetEeaTransactionCount implements JsonRpcMethod { @@ -61,9 +69,16 @@ public class PrivGetEeaTransactionCount implements JsonRpcMethod { final String privateFrom = requestContext.getRequiredParameter(1, String.class); final String[] privateFor = requestContext.getRequiredParameter(2, String[].class); + final String privacyUserId = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); + + if (!privateFrom.equals(privacyUserId)) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY); + } + try { final long nonce = - privacyController.determineEeaNonce( + determineEeaNonce( privateFrom, privateFor, address, @@ -80,4 +95,19 @@ public class PrivGetEeaTransactionCount implements JsonRpcMethod { requestContext.getRequest().getId(), GET_PRIVATE_TRANSACTION_NONCE_ERROR); } } + + private long determineEeaNonce( + final String privateFrom, + final String[] privateFor, + final Address address, + final String privacyUserId) { + + final Bytes from = Bytes.fromBase64String(privateFrom); + final List toAddresses = + Arrays.stream(privateFor).map(Bytes::fromBase64String).collect(Collectors.toList()); + + final Bytes32 privacyGroupId = PrivacyGroupUtil.calculateEeaPrivacyGroupId(from, toAddresses); + return privacyController.determineNonce( + address, privacyGroupId.toBase64String(), privacyUserId); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java index 1814bc544..dfde7bfbd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.PrivacyQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Collections; @@ -72,13 +73,9 @@ public class PrivGetLogs implements JsonRpcMethod { filter .getBlockHash() .map( - blockHash -> { - return findLogsForBlockHash(requestContext, privacyGroupId, filter, blockHash); - }) - .orElseGet( - () -> { - return findLogsForBlockRange(requestContext, privacyGroupId, filter); - }); + blockHash -> + findLogsForBlockHash(requestContext, privacyGroupId, filter, blockHash)) + .orElseGet(() -> findLogsForBlockRange(requestContext, privacyGroupId, filter)); return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), new LogsResult(matchingLogs)); @@ -88,11 +85,16 @@ public class PrivGetLogs implements JsonRpcMethod { final JsonRpcRequestContext requestContext, final String privacyGroupId, final FilterParameter filter) { + + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey( + requestContext, privacyGroupId, Optional.empty()); + } + final long fromBlockNumber = filter.getFromBlock().getNumber().orElse(0L); final long toBlockNumber = filter.getToBlock().getNumber().orElse(blockchainQueries.headBlockNumber()); - PrivUtil.checkMembershipForAuthenticatedUser( - privacyController, privacyIdProvider, requestContext, privacyGroupId, toBlockNumber); + return privacyQueries.matchingLogs( privacyGroupId, fromBlockNumber, toBlockNumber, filter.getLogsQuery()); } @@ -107,8 +109,21 @@ public class PrivGetLogs implements JsonRpcMethod { return Collections.emptyList(); } final long blockNumber = blockHeader.get().getNumber(); - PrivUtil.checkMembershipForAuthenticatedUser( - privacyController, privacyIdProvider, requestContext, privacyGroupId, blockNumber); + + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey( + requestContext, privacyGroupId, Optional.of(Long.valueOf(blockNumber))); + } + return privacyQueries.matchingLogs(privacyGroupId, blockHash, filter.getLogsQuery()); } + + private void checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey( + final JsonRpcRequestContext request, + final String privacyGroupId, + final Optional toBlock) { + final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); + privacyController.verifyPrivacyGroupContainsPrivacyUserId( + privacyGroupId, privacyUserId, toBlock); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java index adbdd7ef3..2d7d86e5f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java @@ -61,7 +61,7 @@ public class PrivGetTransactionCount implements JsonRpcMethod { try { final long nonce = - privacyController.determineBesuNonce( + privacyController.determineNonce( address, privacyGroupId, privacyIdProvider.getPrivacyUserId(requestContext.getUser())); 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 9bdc7ff9c..8c797540c 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 @@ -24,6 +24,7 @@ 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.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; public class PrivNewFilter implements JsonRpcMethod { @@ -52,8 +53,11 @@ public class PrivNewFilter implements JsonRpcMethod { final FilterParameter filter = request.getRequiredParameter(1, FilterParameter.class); final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); - // no need to pass blockNumber. To create a filter, you need to be a current member of the group - checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(privacyUserId, privacyGroupId); + if (privacyController instanceof MultiTenancyPrivacyController) { + // no need to pass blockNumber. To create a filter, you need to be a current member of the + // group + checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(privacyUserId, privacyGroupId); + } if (!filter.isValid()) { return new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS); 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 2a00f16af..b7a368a02 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 @@ -29,8 +29,8 @@ import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Arrays; -import java.util.List; +import graphql.com.google.common.collect.Lists; import org.apache.logging.log4j.Logger; public class PrivxFindOnchainPrivacyGroup implements JsonRpcMethod { @@ -58,10 +58,10 @@ public class PrivxFindOnchainPrivacyGroup implements JsonRpcMethod { LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); - final List response; + final PrivacyGroup[] response; try { response = - privacyController.findOnchainPrivacyGroupByMembers( + privacyController.findPrivacyGroupByMembers( Arrays.asList(addresses), privacyIdProvider.getPrivacyUserId(requestContext.getUser())); } catch (final MultiTenancyValidationException e) { @@ -74,6 +74,7 @@ public class PrivxFindOnchainPrivacyGroup implements JsonRpcMethod { requestContext.getRequest().getId(), FIND_ONCHAIN_PRIVACY_GROUP_ERROR); } - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response); + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Lists.newArrayList(response)); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java index 152635739..3e352ecdc 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java @@ -25,12 +25,13 @@ 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.MultiTenancyPrivacyController; +import org.hyperledger.besu.ethereum.privacy.OnchainPrivacyController; import org.hyperledger.besu.ethereum.privacy.PluginPrivacyController; 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.RestrictedDefaultPrivacyController; -import org.hyperledger.besu.ethereum.privacy.RestrictedMultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.markertransaction.FixedKeySigningPrivateMarkerTransactionFactory; import org.hyperledger.besu.ethereum.privacy.markertransaction.RandomSigningPrivateMarkerTransactionFactory; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -135,22 +136,29 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho privateNonceProvider, privacyParameters.getPrivateWorldStateReader()); } else { - final RestrictedDefaultPrivacyController restrictedDefaultPrivacyController = - new RestrictedDefaultPrivacyController( - getBlockchainQueries().getBlockchain(), - privacyParameters, - chainId, - createPrivateTransactionSimulator(), - privateNonceProvider, - privacyParameters.getPrivateWorldStateReader()); - + final PrivacyController privacyController; + if (privacyParameters.isOnchainPrivacyGroupsEnabled()) { + privacyController = + new OnchainPrivacyController( + getBlockchainQueries().getBlockchain(), + privacyParameters, + chainId, + createPrivateTransactionSimulator(), + privateNonceProvider, + privacyParameters.getPrivateWorldStateReader()); + } else { + privacyController = + new RestrictedDefaultPrivacyController( + getBlockchainQueries().getBlockchain(), + privacyParameters, + chainId, + createPrivateTransactionSimulator(), + privateNonceProvider, + privacyParameters.getPrivateWorldStateReader()); + } return privacyParameters.isMultiTenancyEnabled() - ? new RestrictedMultiTenancyPrivacyController( - restrictedDefaultPrivacyController, - chainId, - privacyParameters.getEnclave(), - privacyParameters.isOnchainPrivacyGroupsEnabled()) - : restrictedDefaultPrivacyController; + ? new MultiTenancyPrivacyController(privacyController) + : privacyController; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java index 1e6da421e..6ee6db4c0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivacyController; abstract class AbstractPrivateSubscriptionMethod extends AbstractSubscriptionMethod { - private final PrivacyController privacyController; + final PrivacyController privacyController; protected final PrivacyIdProvider privacyIdProvider; AbstractPrivateSubscriptionMethod( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java index 3bb00442a..b3766c85b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.Subscrip 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.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; public class PrivSubscribe extends AbstractPrivateSubscriptionMethod { @@ -49,9 +50,10 @@ public class PrivSubscribe extends AbstractPrivateSubscriptionMethod { final String privacyUserId = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); final PrivateSubscribeRequest subscribeRequest = getMapper().mapPrivateSubscribeRequest(requestContext, privacyUserId); - - checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId( - requestContext, subscribeRequest.getPrivacyGroupId()); + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId( + requestContext, subscribeRequest.getPrivacyGroupId()); + } final Long subscriptionId = subscriptionManager().subscribe(subscribeRequest); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java index 76eff1c2c..513c15c64 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.Subscrip 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.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; public class PrivUnsubscribe extends AbstractPrivateSubscriptionMethod { @@ -49,8 +50,10 @@ public class PrivUnsubscribe extends AbstractPrivateSubscriptionMethod { final PrivateUnsubscribeRequest unsubscribeRequest = getMapper().mapPrivateUnsubscribeRequest(requestContext); - checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId( - requestContext, unsubscribeRequest.getPrivacyGroupId()); + if (privacyController instanceof MultiTenancyPrivacyController) { + checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId( + requestContext, unsubscribeRequest.getPrivacyGroupId()); + } final boolean unsubscribed = subscriptionManager().unsubscribe(unsubscribeRequest); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java index 66420a9bb..4db91fca1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java @@ -22,12 +22,13 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.privacy.ChainHeadPrivateNonceProvider; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; +import org.hyperledger.besu.ethereum.privacy.OnchainPrivacyController; import org.hyperledger.besu.ethereum.privacy.PluginPrivacyController; 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.RestrictedDefaultPrivacyController; -import org.hyperledger.besu.ethereum.privacy.RestrictedMultiTenancyPrivacyController; import java.math.BigInteger; import java.util.Collection; @@ -75,21 +76,29 @@ public class PrivateWebSocketMethodsFactory { createPrivateNonceProvider(), privacyParameters.getPrivateWorldStateReader()); } else { - final RestrictedDefaultPrivacyController restrictedDefaultPrivacyController = - new RestrictedDefaultPrivacyController( - blockchainQueries.getBlockchain(), - privacyParameters, - chainId, - createPrivateTransactionSimulator(), - createPrivateNonceProvider(), - privacyParameters.getPrivateWorldStateReader()); + final PrivacyController restrictedPrivacyController; + if (privacyParameters.isOnchainPrivacyGroupsEnabled()) { + restrictedPrivacyController = + new OnchainPrivacyController( + blockchainQueries.getBlockchain(), + privacyParameters, + chainId, + createPrivateTransactionSimulator(), + createPrivateNonceProvider(), + privacyParameters.getPrivateWorldStateReader()); + } else { + restrictedPrivacyController = + new RestrictedDefaultPrivacyController( + blockchainQueries.getBlockchain(), + privacyParameters, + chainId, + createPrivateTransactionSimulator(), + createPrivateNonceProvider(), + privacyParameters.getPrivateWorldStateReader()); + } return privacyParameters.isMultiTenancyEnabled() - ? new RestrictedMultiTenancyPrivacyController( - restrictedDefaultPrivacyController, - chainId, - privacyParameters.getEnclave(), - privacyParameters.isOnchainPrivacyGroupsEnabled()) - : restrictedDefaultPrivacyController; + ? new MultiTenancyPrivacyController(restrictedPrivacyController) + : restrictedPrivacyController; } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransactionTest.java index 0b42fa64a..534622eeb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransactionTest.java @@ -80,12 +80,12 @@ public class RestrictedOffchainEeaSendRawTransactionTest extends BaseEeaSendRawT when(privacyController.createPrivateMarkerTransactionPayload(any(), any(), any())) .thenReturn(MOCK_ORION_KEY); - Optional pantheonPrivacyGroup = + final Optional pantheonPrivacyGroup = Optional.of( new PrivacyGroup( "", PrivacyGroup.Type.PANTHEON, "", "", singletonList(ENCLAVE_PUBLIC_KEY))); - when(privacyController.findOffchainPrivacyGroupByGroupId(any(), any())) + when(privacyController.findPrivacyGroupByGroupId(any(), any())) .thenReturn(pantheonPrivacyGroup); when(transactionPool.addLocalTransaction(any())).thenReturn(ValidationResult.valid()); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransactionTest.java index cecc0dbec..9aca32542 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransactionTest.java @@ -70,8 +70,7 @@ public class RestrictedOnchainEeaSendRawTransactionTest extends BaseEeaSendRawTr new PrivacyGroup( "", PrivacyGroup.Type.ONCHAIN, "", "", Arrays.asList(ENCLAVE_PUBLIC_KEY))); - when(privacyController.findOnchainPrivacyGroupAndAddNewMembers(any(), any(), any())) - .thenReturn(onchainPrivacyGroup); + when(privacyController.findPrivacyGroupByGroupId(any(), any())).thenReturn(onchainPrivacyGroup); final JsonRpcSuccessResponse expectedResponse = new JsonRpcSuccessResponse( @@ -104,8 +103,7 @@ public class RestrictedOnchainEeaSendRawTransactionTest extends BaseEeaSendRawTr when(privacyController.validatePrivateTransaction(any(), any())) .thenReturn(ValidationResult.valid()); - when(privacyController.findOnchainPrivacyGroupAndAddNewMembers(any(), any(), any())) - .thenReturn(Optional.empty()); + when(privacyController.findPrivacyGroupByGroupId(any(), any())).thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java index 6016ec872..4bb5dab93 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java @@ -15,12 +15,10 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,7 +35,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSucces import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.RestrictedDefaultPrivacyController; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -183,19 +180,6 @@ public class PrivCallTest { .hasMessage("Missing required json rpc parameter at index 0"); } - @Test - public void multiTenancyCheckFailure() { - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId( - eq(privacyGroupId), eq(ENCLAVE_PUBLIC_KEY), eq(Optional.of(1L))); - - final JsonRpcRequestContext request = ethCallRequest(privacyGroupId, callParameter(), "0x02"); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class); - } - private JsonCallParameter callParameter() { return new JsonCallParameter( Address.fromHexString("0x0"), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java index 6fc904eb4..c199408a4 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java @@ -89,8 +89,6 @@ public class PrivDebugGetStateRootTest { @Test public void shouldReturnErrorIfInvalidGroupId() { when(privacyController.findPrivacyGroupByGroupId(anyString(), anyString())) - .thenCallRealMethod(); - when(privacyController.findOffchainPrivacyGroupByGroupId(anyString(), anyString())) .thenReturn(Optional.empty()); final JsonRpcResponse response = method.response(request("not_base64", "latest")); assertThat(response.getType()).isEqualByComparingTo(JsonRpcResponseType.ERROR); 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 c28e47e7d..184a529e2 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 @@ -77,7 +77,7 @@ public class PrivFindPrivacyGroupTest { @SuppressWarnings("unchecked") @Test public void findsPrivacyGroupWithValidAddresses() { - when(privacyController.findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) .thenReturn(new PrivacyGroup[] {privacyGroup}); final PrivFindPrivacyGroup privFindPrivacyGroup = @@ -88,12 +88,12 @@ public class PrivFindPrivacyGroupTest { final List result = (List) response.getResult(); assertThat(result).hasSize(1); assertThat(result.get(0)).isEqualToComparingFieldByField(privacyGroup); - verify(privacyController).findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } @Test public void failsWithFindPrivacyGroupErrorIfEnclaveFails() { - when(privacyController.findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) .thenThrow(new EnclaveClientException(500, "some failure")); final PrivFindPrivacyGroup privFindPrivacyGroup = new PrivFindPrivacyGroup(privacyController, privacyIdProvider); @@ -101,12 +101,12 @@ public class PrivFindPrivacyGroupTest { final JsonRpcErrorResponse response = (JsonRpcErrorResponse) privFindPrivacyGroup.response(request); assertThat(response.getError()).isEqualTo(JsonRpcError.FIND_PRIVACY_GROUP_ERROR); - verify(privacyController).findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } @Test public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() { - when(privacyController.findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) .thenThrow(new MultiTenancyValidationException("validation failed")); final PrivFindPrivacyGroup privFindPrivacyGroup = new PrivFindPrivacyGroup(privacyController, privacyIdProvider); @@ -116,6 +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).findOffchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java index 6c0a7d6b7..62e0f505a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -36,20 +38,24 @@ import org.junit.Test; public class PrivGetEeaTransactionCountTest { private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String[] PRIVATE_FOR = { + "sgFkVOyFndZe/5SAZJO5UYbrl7pezHetveriBBWWnE8=", + "R1kW75NQC9XX3kwNpyPjCBFflM29+XvnKKS9VLrUkzo=", + "QzHuACXpfhoGAgrQriWJcDJ6MrUwcCvutKMoAn9KplQ=" + }; private final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); private final PrivacyController privacyController = mock(PrivacyController.class); private JsonRpcRequestContext request; - private final String privateFrom = "thePrivateFromKey"; - private final String[] privateFor = new String[] {"first", "second", "third"}; - private final Address address = Address.fromHexString("55"); + private final Address address = + Address.fromHexString("0x1000000000000000000000000000000000000001"); private final PrivacyIdProvider privacyIdProvider = (user) -> ENCLAVE_PUBLIC_KEY; @Before public void setup() { when(privacyParameters.isEnabled()).thenReturn(true); - final Object[] jsonBody = new Object[] {address.toString(), privateFrom, privateFor}; + final Object[] jsonBody = new Object[] {address.toString(), ENCLAVE_PUBLIC_KEY, PRIVATE_FOR}; request = new JsonRpcRequestContext( new JsonRpcRequest("2.0", "priv_getEeaTransactionCount", jsonBody)); @@ -61,7 +67,7 @@ public class PrivGetEeaTransactionCountTest { final PrivGetEeaTransactionCount method = new PrivGetEeaTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(any(), any(), eq(ENCLAVE_PUBLIC_KEY))) .thenReturn(reportedNonce); final JsonRpcResponse response = method.response(request); @@ -77,7 +83,7 @@ public class PrivGetEeaTransactionCountTest { final PrivGetEeaTransactionCount method = new PrivGetEeaTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(any(), any(), eq(ENCLAVE_PUBLIC_KEY))) .thenThrow(EnclaveClientException.class); final JsonRpcResponse response = method.response(request); @@ -93,7 +99,7 @@ public class PrivGetEeaTransactionCountTest { final PrivGetEeaTransactionCount method = new PrivGetEeaTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(any(), any(), eq(ENCLAVE_PUBLIC_KEY))) .thenThrow(EnclaveClientException.class); final JsonRpcResponse response = method.response(request); @@ -109,7 +115,7 @@ public class PrivGetEeaTransactionCountTest { final PrivGetEeaTransactionCount method = new PrivGetEeaTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineEeaNonce(privateFrom, privateFor, address, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(any(), any(), eq(ENCLAVE_PUBLIC_KEY))) .thenThrow(new MultiTenancyValidationException("validation failed")); final JsonRpcResponse response = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java index 24b8c0285..d7a7c5caa 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java @@ -37,8 +37,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogsResult; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; -import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Collections; import java.util.List; @@ -60,7 +60,7 @@ public class PrivGetFilterChangesTest { private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; @Mock private FilterManager filterManager; - @Mock private PrivacyController privacyController; + @Mock private MultiTenancyPrivacyController privacyController; @Mock private PrivacyIdProvider privacyIdProvider; private PrivGetFilterChanges method; @@ -84,6 +84,23 @@ public class PrivGetFilterChangesTest { .hasMessageContaining("Missing required json rpc parameter at index 0"); } + @Test + public void multiTenancyCheckFailure() { + final User user = mock(User.class); + + when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); + doThrow(new MultiTenancyValidationException("msg")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); + + final JsonRpcRequestContext request = + privGetFilterChangesRequestWithUser(PRIVACY_GROUP_ID, FILTER_ID, user); + + assertThatThrownBy(() -> method.response(request)) + .isInstanceOf(MultiTenancyValidationException.class) + .hasMessageContaining("msg"); + } + @Test public void filterIdIsRequired() { final JsonRpcRequestContext request = privGetFilterChangesRequest(PRIVACY_GROUP_ID, null); @@ -141,23 +158,6 @@ public class PrivGetFilterChangesTest { assertThat(response).isEqualTo(expectedResponse); } - @Test - public void multiTenancyCheckFailure() { - final User user = mock(User.class); - - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - final JsonRpcRequestContext request = - privGetFilterChangesRequestWithUser(PRIVACY_GROUP_ID, FILTER_ID, user); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - private JsonRpcRequestContext privGetFilterChangesRequest( final String privacyGroupId, final String filterId) { return new JsonRpcRequestContext( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java index d3907963e..feb3ac343 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java @@ -16,10 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; 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.verify; import static org.mockito.Mockito.when; @@ -37,14 +34,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogsResult; import org.hyperledger.besu.ethereum.core.LogWithMetadata; -import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Collections; import java.util.List; import com.google.common.collect.Lists; -import io.vertx.ext.auth.User; import org.apache.tuweni.bytes.Bytes; import org.junit.Before; import org.junit.Test; @@ -56,7 +51,6 @@ import org.mockito.junit.MockitoJUnitRunner; public class PrivGetFilterLogsTest { private final String FILTER_ID = "0xdbdb02abb65a2ba57a1cc0336c17ef75"; - private final String ENCLAVE_KEY = "enclave_key"; private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; @Mock private FilterManager filterManager; @@ -141,36 +135,12 @@ public class PrivGetFilterLogsTest { assertThat(response).isEqualTo(expectedResponse); } - @Test - public void multiTenancyCheckFailure() { - final User user = mock(User.class); - - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - final JsonRpcRequestContext request = - privGetFilterLogsRequestWithUser(PRIVACY_GROUP_ID, FILTER_ID, user); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - private JsonRpcRequestContext privGetFilterLogsRequest( final String privacyGroupId, final String filterId) { return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "priv_getFilterLogs", new Object[] {privacyGroupId, filterId})); } - private JsonRpcRequestContext privGetFilterLogsRequestWithUser( - final String privacyGroupId, final String filterId, final User user) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "priv_getFilterLogs", new Object[] {privacyGroupId, filterId}), - user); - } - 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/PrivGetLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java index b03b996c3..d800cc277 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java @@ -18,7 +18,6 @@ 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.verify; import static org.mockito.Mockito.when; @@ -41,7 +40,6 @@ import org.hyperledger.besu.ethereum.api.query.LogsQuery; import org.hyperledger.besu.ethereum.api.query.PrivacyQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.LogWithMetadata; -import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.evm.log.LogTopic; @@ -52,7 +50,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import com.google.common.collect.Lists; -import io.vertx.ext.auth.User; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.Before; @@ -64,7 +61,6 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class PrivGetLogsTest { - private final String ENCLAVE_KEY = "enclave_key"; private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; @Mock private BlockchainQueries blockchainQueries; @@ -208,42 +204,12 @@ public class PrivGetLogsTest { assertThat(logsResult).usingRecursiveComparison().isEqualTo(expectedLogsResult); } - @Test - public void multiTenancyCheckFailure() { - final User user = mock(User.class); - final FilterParameter filterParameter = mock(FilterParameter.class); - final BlockParameter blockParameter = new BlockParameter(100L); - - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - when(filterParameter.isValid()).thenReturn(true); - when(filterParameter.getBlockHash()).thenReturn(Optional.empty()); - when(filterParameter.getFromBlock()).thenReturn(blockParameter); - when(filterParameter.getToBlock()).thenReturn(blockParameter); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId( - eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY), eq(Optional.of(99L))); - - final JsonRpcRequestContext request = - privGetLogRequestWithUser(PRIVACY_GROUP_ID, filterParameter, user); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class); - } - private JsonRpcRequestContext privGetLogRequest( final String privacyGroupId, final FilterParameter filterParameter) { return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "priv_getLogs", new Object[] {privacyGroupId, filterParameter})); } - private JsonRpcRequestContext privGetLogRequestWithUser( - final String privacyGroupId, final FilterParameter filterParameter, final User user) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "priv_getLogs", new Object[] {privacyGroupId, filterParameter}), - user); - } - private List logWithMetadataList(final int length) { return IntStream.range(0, length).mapToObj(this::logWithMetadata).collect(Collectors.toList()); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java index 64891aee2..ca0ce9972 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java @@ -56,7 +56,7 @@ public class PrivGetTransactionCountTest { @Before public void before() { when(privacyParameters.isEnabled()).thenReturn(true); - when(privacyController.determineBesuNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) .thenReturn(NONCE); } @@ -74,8 +74,7 @@ public class PrivGetTransactionCountTest { (JsonRpcSuccessResponse) privGetTransactionCount.response(request); assertThat(response.getResult()).isEqualTo(String.format("0x%X", NONCE)); - verify(privacyController) - .determineBesuNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY); + verify(privacyController).determineNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY); } @Test @@ -83,7 +82,7 @@ public class PrivGetTransactionCountTest { final PrivGetTransactionCount privGetTransactionCount = new PrivGetTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineBesuNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) .thenThrow(EnclaveClientException.class); final Object[] params = new Object[] {senderAddress, PRIVACY_GROUP_ID}; @@ -103,7 +102,7 @@ public class PrivGetTransactionCountTest { final PrivGetTransactionCount privGetTransactionCount = new PrivGetTransactionCount(privacyController, privacyIdProvider); - when(privacyController.determineBesuNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) + when(privacyController.determineNonce(senderAddress, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY)) .thenThrow(new MultiTenancyValidationException("validation failed")); final Object[] params = new Object[] {senderAddress, PRIVACY_GROUP_ID}; 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 52900b9ca..dad2bdd32 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 @@ -16,10 +16,8 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; 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.ArgumentMatchers.refEq; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,7 +35,6 @@ 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.query.LogsQuery; -import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.evm.log.LogTopic; @@ -152,24 +149,6 @@ public class PrivNewFilterTest { eq((expectedQuery))); } - @Test - public void multiTenancyCheckFailure() { - final User user = mock(User.class); - final FilterParameter filterParameter = mock(FilterParameter.class); - - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - final JsonRpcRequestContext request = - privNewFilterRequestWithUser(PRIVACY_GROUP_ID, filterParameter, user); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - private JsonRpcRequestContext privNewFilterRequest( final String privacyGroupId, final FilterParameter filterParameter) { return new JsonRpcRequestContext( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java index 51fd51bb9..f5287a810 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java @@ -16,12 +16,8 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; 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.verify; -import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -29,10 +25,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonR import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivUninstallFilter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; -import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; -import io.vertx.ext.auth.User; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +37,6 @@ import org.mockito.junit.MockitoJUnitRunner; public class PrivUninstallFilterTest { private final String FILTER_ID = "0xdbdb02abb65a2ba57a1cc0336c17ef75"; - private final String ENCLAVE_KEY = "enclave_key"; private final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; @Mock private FilterManager filterManager; @@ -88,33 +81,9 @@ public class PrivUninstallFilterTest { verify(filterManager).uninstallFilter(eq(FILTER_ID)); } - @Test - public void multiTenancyCheckFailure() { - final User user = mock(User.class); - - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - final JsonRpcRequestContext request = - privUninstallFilterRequestWithUser(PRIVACY_GROUP_ID, FILTER_ID, user); - - assertThatThrownBy(() -> method.response(request)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - private JsonRpcRequestContext privUninstallFilterRequest( final String privacyGroupId, final String filterId) { return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "priv_uninstallFilter", new Object[] {privacyGroupId, filterId})); } - - private JsonRpcRequestContext privUninstallFilterRequestWithUser( - final String privacyGroupId, final String filterId, final User user) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "priv_uninstallFilter", new Object[] {privacyGroupId, filterId}), - user); - } } 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 index 870e2689f..a1345e9a3 100644 --- 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 @@ -33,7 +33,6 @@ 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; @@ -83,31 +82,31 @@ public class PrivxFindOnchainPrivacyGroupTest { @SuppressWarnings("unchecked") @Test public void findsPrivacyGroupWithValidAddresses() { - when(privacyController.findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) - .thenReturn(Collections.singletonList(privacyGroup)); + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + .thenReturn(new PrivacyGroup[] {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).findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } @Test public void failsWithFindPrivacyGroupErrorIfEnclaveFails() { - when(privacyController.findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) .thenThrow(new EnclaveClientException(500, "some failure")); final JsonRpcErrorResponse response = (JsonRpcErrorResponse) privxFindOnchainPrivacyGroup.response(request); assertThat(response.getError()).isEqualTo(JsonRpcError.FIND_ONCHAIN_PRIVACY_GROUP_ERROR); - verify(privacyController).findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } @Test public void failsWithUnauthorizedErrorIfMultiTenancyValidationFails() { - when(privacyController.findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) + when(privacyController.findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY)) .thenThrow(new MultiTenancyValidationException("validation failed")); final JsonRpcResponse expectedResponse = @@ -115,6 +114,6 @@ public class PrivxFindOnchainPrivacyGroupTest { request.getRequest().getId(), JsonRpcError.FIND_ONCHAIN_PRIVACY_GROUP_ERROR); final JsonRpcResponse response = privxFindOnchainPrivacyGroup.response(request); assertThat(response).isEqualTo(expectedResponse); - verify(privacyController).findOnchainPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); + verify(privacyController).findPrivacyGroupByMembers(ADDRESSES, ENCLAVE_PUBLIC_KEY); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java index cb92f0627..26c0fa69f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java @@ -32,8 +32,8 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; 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.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; -import org.hyperledger.besu.ethereum.privacy.RestrictedMultiTenancyPrivacyController; import org.hyperledger.besu.plugin.services.privacy.PrivateMarkerTransactionFactory; import java.util.Map; @@ -154,7 +154,7 @@ public class PrivacyApiGroupJsonRpcMethodsTest { privacyApiGroupJsonRpcMethods.create(); final PrivacyController privacyController = privacyApiGroupJsonRpcMethods.privacyController; - assertThat(privacyController).isInstanceOf(RestrictedMultiTenancyPrivacyController.class); + assertThat(privacyController).isInstanceOf(MultiTenancyPrivacyController.class); } private User createUser(final String enclavePublicKey) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java index 4812da106..522650edb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java @@ -15,10 +15,8 @@ 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; @@ -34,7 +32,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request. 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; @@ -110,33 +107,6 @@ public class PrivSubscribeTest { 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, - "public_key"); - - when(mapperMock.mapPrivateSubscribeRequest(any(), any())).thenReturn(subscribeRequest); - when(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - assertThatThrownBy(() -> privSubscribe.response(jsonRpcrequestContext)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - @Test public void multiTenancyCheckSuccess() { final User user = mock(User.class); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java index d123f99c1..9131032d2 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java @@ -15,10 +15,8 @@ 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; @@ -33,11 +31,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.Subscrip 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; @@ -47,7 +43,6 @@ 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"; @@ -110,25 +105,6 @@ public class PrivUnsubscribeTest { 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(privacyIdProvider.getPrivacyUserId(any())).thenReturn(ENCLAVE_KEY); - doThrow(new MultiTenancyValidationException("msg")) - .when(privacyController) - .verifyPrivacyGroupContainsPrivacyUserId(eq(PRIVACY_GROUP_ID), eq(ENCLAVE_KEY)); - - assertThatThrownBy(() -> privUnsubscribe.response(jsonRpcrequestContext)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessageContaining("msg"); - } - private JsonRpcRequestContext createPrivUnsubscribeRequest() { return new JsonRpcRequestContext( Json.decodeValue( @@ -137,14 +113,4 @@ public class PrivUnsubscribeTest { + "\", \"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); - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java new file mode 100644 index 000000000..dd8d171c8 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java @@ -0,0 +1,122 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.transaction.CallParameter; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; + +import java.math.BigInteger; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public abstract class AbstractPrivacyController implements PrivacyController { + + final Blockchain blockchain; + final PrivateStateStorage privateStateStorage; + final PrivateTransactionValidator privateTransactionValidator; + final PrivateTransactionSimulator privateTransactionSimulator; + final PrivateNonceProvider privateNonceProvider; + final PrivateWorldStateReader privateWorldStateReader; + final PrivateStateRootResolver privateStateRootResolver; + + protected AbstractPrivacyController( + final Blockchain blockchain, + final PrivacyParameters privacyParameters, + final Optional chainId, + final PrivateTransactionSimulator privateTransactionSimulator, + final PrivateNonceProvider privateNonceProvider, + final PrivateWorldStateReader privateWorldStateReader) { + this( + blockchain, + privacyParameters.getPrivateStateStorage(), + new PrivateTransactionValidator(chainId), + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privacyParameters.getPrivateStateRootResolver()); + } + + protected AbstractPrivacyController( + final Blockchain blockchain, + final PrivateStateStorage privateStateStorage, + final PrivateTransactionValidator privateTransactionValidator, + final PrivateTransactionSimulator privateTransactionSimulator, + final PrivateNonceProvider privateNonceProvider, + final PrivateWorldStateReader privateWorldStateReader, + final PrivateStateRootResolver privateStateRootResolver) { + this.blockchain = blockchain; + this.privateStateStorage = privateStateStorage; + this.privateTransactionValidator = privateTransactionValidator; + this.privateTransactionSimulator = privateTransactionSimulator; + this.privateNonceProvider = privateNonceProvider; + this.privateWorldStateReader = privateWorldStateReader; + this.privateStateRootResolver = privateStateRootResolver; + } + + @Override + public ValidationResult validatePrivateTransaction( + final PrivateTransaction privateTransaction, final String privacyUserId) { + final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); + return privateTransactionValidator.validate( + privateTransaction, + determineNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), + true); + } + + @Override + public long determineNonce( + final Address sender, final String privacyGroupId, final String privacyUserId) { + return privateNonceProvider.getNonce( + sender, Bytes32.wrap(Bytes.fromBase64String(privacyGroupId))); + } + + @Override + public Optional simulatePrivateTransaction( + final String privacyGroupId, + final String privacyUserId, + final CallParameter callParams, + final long blockNumber) { + return privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber); + } + + @Override + public Optional getContractCode( + final String privacyGroupId, + final Address contractAddress, + final Hash blockHash, + final String privacyUserId) { + return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress); + } + + @Override + public Optional getStateRootByBlockNumber( + final String privacyGroupId, final String privacyUserId, final long blockNumber) { + return blockchain + .getBlockByNumber(blockNumber) + .map( + block -> + privateStateRootResolver.resolveLastStateRoot( + Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)), block.getHash())); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java new file mode 100644 index 000000000..75d0df629 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java @@ -0,0 +1,56 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; + +import java.util.Optional; + +public abstract class AbstractRestrictedPrivacyController extends AbstractPrivacyController { + + final Enclave enclave; + final PrivateTransactionLocator privateTransactionLocator; + + protected AbstractRestrictedPrivacyController( + final Blockchain blockchain, + final PrivateStateStorage privateStateStorage, + final Enclave enclave, + final PrivateTransactionValidator privateTransactionValidator, + final PrivateTransactionSimulator privateTransactionSimulator, + final PrivateNonceProvider privateNonceProvider, + final PrivateWorldStateReader privateWorldStateReader, + final PrivateStateRootResolver privateStateRootResolver) { + super( + blockchain, + privateStateStorage, + privateTransactionValidator, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privateStateRootResolver); + this.enclave = enclave; + this.privateTransactionLocator = + new PrivateTransactionLocator(blockchain, enclave, privateStateStorage); + } + + @Override + public Optional findPrivateTransactionByPmtHash( + final Hash pmtHash, final String enclaveKey) { + return privateTransactionLocator.findByPmtHash(pmtHash, enclaveKey); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/ChainHeadPrivateNonceProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/ChainHeadPrivateNonceProvider.java index a7e7710e8..b457a5e98 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/ChainHeadPrivateNonceProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/ChainHeadPrivateNonceProvider.java @@ -40,7 +40,7 @@ public class ChainHeadPrivateNonceProvider implements PrivateNonceProvider { @Override public long getNonce(final Address sender, final Bytes32 privacyGroupId) { final BlockHeader chainHeadHeader = blockchain.getChainHeadHeader(); - Hash chainHeadHash = chainHeadHeader.getHash(); + final Hash chainHeadHash = chainHeadHeader.getHash(); final Hash stateRoot = privateStateRootResolver.resolveLastStateRoot(privacyGroupId, chainHeadHash); return privateWorldStateArchive 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 new file mode 100644 index 000000000..192c376a0 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java @@ -0,0 +1,165 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.enclave.types.PrivacyGroup; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.transaction.CallParameter; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; + +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class MultiTenancyPrivacyController implements PrivacyController { + + private final PrivacyController privacyController; + + public MultiTenancyPrivacyController(final PrivacyController privacyController) { + this.privacyController = privacyController; + } + + @Override + public Optional findPrivateTransactionByPmtHash( + final Hash pmtHash, final String enclaveKey) { + return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey); + } + + @Override + public String createPrivateMarkerTransactionPayload( + final PrivateTransaction privateTransaction, + final String privacyUserId, + final Optional maybePrivacyGroup) { + final Optional maybePrivacyGroupId = privateTransaction.getPrivacyGroupId(); + if (maybePrivacyGroupId.isPresent()) { + verifyPrivacyGroupContainsPrivacyUserId( + maybePrivacyGroupId.get().toBase64String(), privacyUserId); + } + return privacyController.createPrivateMarkerTransactionPayload( + privateTransaction, privacyUserId, maybePrivacyGroup); + } + + @Override + public PrivacyGroup createPrivacyGroup( + final List addresses, + final String name, + final String description, + final String privacyUserId) { + if (!addresses.contains(privacyUserId)) { + throw new MultiTenancyValidationException( + "Privacy group addresses must contain the enclave public key"); + } + return privacyController.createPrivacyGroup(addresses, name, description, privacyUserId); + } + + @Override + public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); + return privacyController.deletePrivacyGroup(privacyGroupId, privacyUserId); + } + + @Override + public PrivacyGroup[] findPrivacyGroupByMembers( + final List addresses, final String privacyUserId) { + if (!addresses.contains(privacyUserId)) { + throw new MultiTenancyValidationException( + "Privacy group addresses must contain the enclave public key"); + } + return privacyController.findPrivacyGroupByMembers(addresses, privacyUserId); + } + + @Override + public ValidationResult validatePrivateTransaction( + final PrivateTransaction privateTransaction, final String privacyUserId) { + + final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); + return privacyController.validatePrivateTransaction(privateTransaction, privacyUserId); + } + + @Override + public long determineNonce( + final Address sender, final String privacyGroupId, final String privacyUserId) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); + return privacyController.determineNonce(sender, privacyGroupId, privacyUserId); + } + + @Override + public Optional simulatePrivateTransaction( + final String privacyGroupId, + final String privacyUserId, + final CallParameter callParams, + final long blockNumber) { + verifyPrivacyGroupContainsPrivacyUserId( + privacyGroupId, privacyUserId, Optional.of(blockNumber)); + return privacyController.simulatePrivateTransaction( + privacyGroupId, privacyUserId, callParams, blockNumber); + } + + @Override + public Optional findPrivacyGroupByGroupId( + final String privacyGroupId, final String privacyUserId) { + final Optional maybePrivacyGroup = + privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId); + checkGroupParticipation(maybePrivacyGroup, privacyUserId); + return maybePrivacyGroup; + } + + private void checkGroupParticipation( + final Optional maybePrivacyGroup, final String enclaveKey) { + if (maybePrivacyGroup.isPresent() + && !maybePrivacyGroup.get().getMembers().contains(enclaveKey)) { + throw new MultiTenancyValidationException( + "Privacy group must contain the enclave public key"); + } + } + + @Override + public Optional getContractCode( + final String privacyGroupId, + final Address contractAddress, + final Hash blockHash, + final String privacyUserId) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); + return privacyController.getContractCode( + privacyGroupId, contractAddress, blockHash, privacyUserId); + } + + @Override + public void verifyPrivacyGroupContainsPrivacyUserId( + final String privacyGroupId, final String privacyUserId) { + privacyController.verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); + } + + @Override + public void verifyPrivacyGroupContainsPrivacyUserId( + final String privacyGroupId, final String privacyUserId, final Optional blockNumber) + throws MultiTenancyValidationException { + privacyController.verifyPrivacyGroupContainsPrivacyUserId( + privacyGroupId, privacyUserId, blockNumber); + } + + @Override + public Optional getStateRootByBlockNumber( + final String privacyGroupId, final String privacyUserId, final long blockNumber) { + verifyPrivacyGroupContainsPrivacyUserId( + privacyGroupId, privacyUserId, Optional.of(blockNumber)); + return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyController.java new file mode 100644 index 000000000..b3294d31a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyController.java @@ -0,0 +1,364 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY_PROXY; +import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.GET_PARTICIPANTS_METHOD_SIGNATURE; +import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.GET_VERSION_METHOD_SIGNATURE; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.types.PrivacyGroup; +import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.transaction.CallParameter; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Optional; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class OnchainPrivacyController extends AbstractRestrictedPrivacyController { + + private static final Logger LOG = LogManager.getLogger(); + + private OnchainPrivacyGroupContract onchainPrivacyGroupContract; + + public OnchainPrivacyController( + final Blockchain blockchain, + final PrivacyParameters privacyParameters, + final Optional chainId, + final PrivateTransactionSimulator privateTransactionSimulator, + final PrivateNonceProvider privateNonceProvider, + final PrivateWorldStateReader privateWorldStateReader) { + this( + blockchain, + privacyParameters.getPrivateStateStorage(), + privacyParameters.getEnclave(), + new PrivateTransactionValidator(chainId), + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privacyParameters.getPrivateStateRootResolver()); + } + + public OnchainPrivacyController( + final Blockchain blockchain, + final PrivateStateStorage privateStateStorage, + final Enclave enclave, + final PrivateTransactionValidator privateTransactionValidator, + final PrivateTransactionSimulator privateTransactionSimulator, + final PrivateNonceProvider privateNonceProvider, + final PrivateWorldStateReader privateWorldStateReader, + final PrivateStateRootResolver privateStateRootResolver) { + super( + blockchain, + privateStateStorage, + enclave, + privateTransactionValidator, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privateStateRootResolver); + + onchainPrivacyGroupContract = new OnchainPrivacyGroupContract(privateTransactionSimulator); + } + + @Override + public String createPrivateMarkerTransactionPayload( + final PrivateTransaction privateTransaction, + final String privacyUserId, + final Optional privacyGroup) { + final String firstPart; + try { + LOG.trace("Storing private transaction in enclave"); + final SendResponse sendResponse = sendRequest(privateTransaction, privacyGroup); + firstPart = sendResponse.getKey(); + } catch (final Exception e) { + LOG.error("Failed to store private transaction in enclave", e); + throw e; + } + final Optional optionalSecondPart = + buildAndSendAddPayload( + privateTransaction, + Bytes32.wrap(privateTransaction.getPrivacyGroupId().orElseThrow()), + privacyUserId); + + return buildCompoundLookupId(firstPart, optionalSecondPart); + } + + @Override + public Optional findPrivacyGroupByGroupId( + final String privacyGroupId, final String enclaveKey) { + // get the privateFor list from the management contract + final Optional privateTransactionSimulatorResultOptional = + privateTransactionSimulator.process( + privacyGroupId, buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE)); + + if (privateTransactionSimulatorResultOptional.isPresent() + && privateTransactionSimulatorResultOptional.get().isSuccessful()) { + final RLPInput rlpInput = + RLP.input(privateTransactionSimulatorResultOptional.get().getOutput()); + if (rlpInput.nextSize() > 0) { + return Optional.of( + new PrivacyGroup( + privacyGroupId, + PrivacyGroup.Type.ONCHAIN, + "", + "", + decodeParticipantList(rlpInput.raw()))); + } + } + return Optional.empty(); + } + + @Override + public PrivacyGroup[] findPrivacyGroupByMembers( + final List addresses, final String privacyUserId) { + final ArrayList privacyGroups = new ArrayList<>(); + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) + .orElse(PrivacyGroupHeadBlockMap.empty()); + privacyGroupHeadBlockMap + .keySet() + .forEach( + c -> { + final Optional maybePrivacyGroup = + findPrivacyGroupByGroupId(c.toBase64String(), privacyUserId); + if (maybePrivacyGroup.isPresent() + && maybePrivacyGroup.get().getMembers().containsAll(addresses)) { + privacyGroups.add(maybePrivacyGroup.get()); + } + }); + return privacyGroups.toArray(new PrivacyGroup[0]); + } + + @Override + public PrivacyGroup createPrivacyGroup( + final List addresses, + final String name, + final String description, + final String privacyUserId) { + throw new PrivacyConfigurationNotSupportedException( + "Method not supported when using onchain privacy"); + } + + @Override + public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { + throw new PrivacyConfigurationNotSupportedException( + "Method not supported when using onchain privacy"); + } + + @Override + public void verifyPrivacyGroupContainsPrivacyUserId( + final String privacyGroupId, final String privacyUserId) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId, Optional.empty()); + } + + @Override + public void verifyPrivacyGroupContainsPrivacyUserId( + final String privacyGroupId, final String privacyUserId, final Optional blockNumber) { + final Optional maybePrivacyGroup = + onchainPrivacyGroupContract.getPrivacyGroupByIdAndBlockNumber(privacyGroupId, blockNumber); + // IF the group exists, check member + // ELSE member is valid if the group doesn't exist yet - this is normal for onchain privacy + // groups + maybePrivacyGroup.ifPresent( + group -> { + if (!group.getMembers().contains(privacyUserId)) { + throw new MultiTenancyValidationException( + "Privacy group must contain the enclave public key"); + } + }); + } + + private List decodeParticipantList(final Bytes rlpEncodedList) { + final ArrayList decodedElements = new ArrayList<>(); + // first 32 bytes is dynamic list offset + final UInt256 lengthOfList = UInt256.fromBytes(rlpEncodedList.slice(32, 32)); // length of list + for (int i = 0; i < lengthOfList.toLong(); ++i) { + decodedElements.add( + Bytes.wrap(rlpEncodedList.slice(64 + (32 * i), 32)).toBase64String()); // participant + } + return decodedElements; + } + + private List buildTransactionMetadataList( + final Bytes privacyGroupId) { + final List pmtHashes = new ArrayList<>(); + PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) + .orElse(PrivacyGroupHeadBlockMap.empty()); + if (privacyGroupHeadBlockMap.containsKey(privacyGroupId)) { + Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); + while (blockHash != null) { + pmtHashes.addAll( + 0, + privateStateStorage + .getPrivateBlockMetadata(blockHash, Bytes32.wrap(privacyGroupId)) + .orElseThrow() + .getPrivateTransactionMetadataList()); + blockHash = blockchain.getBlockHeader(blockHash).orElseThrow().getParentHash(); + privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockHash) + .orElse(PrivacyGroupHeadBlockMap.empty()); + if (privacyGroupHeadBlockMap.containsKey(privacyGroupId)) { + blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); + } else { + break; + } + } + } + return pmtHashes; + } + + private List retrievePrivateTransactions( + final Bytes32 privacyGroupId, + final List privateTransactionMetadataList, + final String privacyUserId) { + final ArrayList privateTransactions = new ArrayList<>(); + privateStateStorage + .getAddDataKey(privacyGroupId) + .ifPresent(key -> privateTransactions.addAll(retrieveAddBlob(key.toBase64String()))); + for (int i = privateTransactions.size(); i < privateTransactionMetadataList.size(); i++) { + final PrivateTransactionMetadata privateTransactionMetadata = + privateTransactionMetadataList.get(i); + final Transaction privateMarkerTransaction = + blockchain + .getTransactionByHash(privateTransactionMetadata.getPrivateMarkerTransactionHash()) + .orElseThrow(); + final ReceiveResponse receiveResponse = + retrieveTransaction( + privateMarkerTransaction.getPayload().slice(0, 32).toBase64String(), privacyUserId); + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); + input.enterList(); + privateTransactions.add( + new PrivateTransactionWithMetadata( + PrivateTransaction.readFrom(input), privateTransactionMetadata)); + input.leaveListLenient(); + } + + return privateTransactions; + } + + private List retrieveAddBlob(final String addDataKey) { + final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey); + return PrivateTransactionWithMetadata.readListFromPayload( + Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload()))); + } + + private Optional buildAndSendAddPayload( + final PrivateTransaction privateTransaction, + final Bytes32 privacyGroupId, + final String privacyUserId) { + if (OnchainUtil.isGroupAdditionTransaction(privateTransaction)) { + final List privateTransactionMetadataList = + buildTransactionMetadataList(privacyGroupId); + if (!privateTransactionMetadataList.isEmpty()) { + final List privateTransactionWithMetadataList = + retrievePrivateTransactions( + privacyGroupId, privateTransactionMetadataList, privacyUserId); + final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList); + final List privateFor = + OnchainUtil.getParticipantsFromParameter(privateTransaction.getPayload()); + return Optional.of( + enclave.send(bytes.toBase64String(), privacyUserId, privateFor).getKey()); + } + } + return Optional.empty(); + } + + private String buildCompoundLookupId( + final String privateTransactionLookupId, + final Optional maybePrivateTransactionLookupId) { + return maybePrivateTransactionLookupId.isPresent() + ? Bytes.concatenate( + Bytes.fromBase64String(privateTransactionLookupId), + Bytes.fromBase64String(maybePrivateTransactionLookupId.get())) + .toBase64String() + : privateTransactionLookupId; + } + + private Bytes serializeAddToGroupPayload( + final List privateTransactionWithMetadataList) { + + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + privateTransactionWithMetadataList.forEach( + privateTransactionWithMetadata -> privateTransactionWithMetadata.writeTo(rlpOutput)); + rlpOutput.endList(); + + return rlpOutput.encoded(); + } + + private SendResponse sendRequest( + final PrivateTransaction privateTransaction, final Optional maybePrivacyGroup) { + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + + final PrivacyGroup privacyGroup = maybePrivacyGroup.orElseThrow(); + final Optional version = + privateTransactionSimulator.process( + privateTransaction.getPrivacyGroupId().orElseThrow().toBase64String(), + buildCallParams(GET_VERSION_METHOD_SIGNATURE)); + new VersionedPrivateTransaction(privateTransaction, version).writeTo(rlpOutput); + final List onchainPrivateFor = privacyGroup.getMembers(); + return enclave.send( + rlpOutput.encoded().toBase64String(), + privateTransaction.getPrivateFrom().toBase64String(), + onchainPrivateFor); + } + + CallParameter buildCallParams(final Bytes methodCall) { + return new CallParameter( + Address.ZERO, ONCHAIN_PRIVACY_PROXY, 3000000, Wei.of(1000), Wei.ZERO, methodCall); + } + + ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { + return enclave.receive(enclaveKey, privacyUserId); + } + + @VisibleForTesting + public void setOnchainPrivacyGroupContract( + final OnchainPrivacyGroupContract onchainPrivacyGroupContract) { + this.onchainPrivacyGroupContract = onchainPrivacyGroupContract; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainUtil.java new file mode 100644 index 000000000..e3bddc453 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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; + +import static org.hyperledger.besu.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY_PROXY; +import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE; + +import org.hyperledger.besu.datatypes.Address; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class OnchainUtil { + + private OnchainUtil() {} + + public static boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { + final Optional
to = privateTransaction.getTo(); + return to.isPresent() + && to.get().equals(ONCHAIN_PRIVACY_PROXY) + && privateTransaction + .getPayload() + .toHexString() + .startsWith(ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString()); + } + + public static List getParticipantsFromParameter(final Bytes input) { + final List participants = new ArrayList<>(); + final Bytes mungedParticipants = input.slice(4 + 32 + 32); + for (int i = 0; i <= mungedParticipants.size() - 32; i += 32) { + participants.add(mungedParticipants.slice(i, 32).toBase64String()); + } + return participants; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java index 07900d950..22cb76d45 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java @@ -16,38 +16,22 @@ package org.hyperledger.besu.ethereum.privacy; import static org.hyperledger.besu.ethereum.privacy.PrivateTransaction.readFrom; -import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.TransactionLocation; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.transaction.CallParameter; -import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.plugin.services.PrivacyPluginService; import java.math.BigInteger; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +public class PluginPrivacyController extends AbstractPrivacyController { -public class PluginPrivacyController implements PrivacyController { - private final PrivateTransactionValidator privateTransactionValidator; - private final PrivateStateRootResolver privateStateRootResolver; - private final Blockchain blockchain; - private final PrivateTransactionSimulator privateTransactionSimulator; - private final PrivateNonceProvider privateNonceProvider; - private final PrivateWorldStateReader privateWorldStateReader; private final PrivacyPluginService privacyPluginService; public PluginPrivacyController( @@ -57,12 +41,13 @@ public class PluginPrivacyController implements PrivacyController { final PrivateTransactionSimulator privateTransactionSimulator, final PrivateNonceProvider privateNonceProvider, final PrivateWorldStateReader privateWorldStateReader) { - this.privateTransactionValidator = new PrivateTransactionValidator(chainId); - this.blockchain = blockchain; - this.privateTransactionSimulator = privateTransactionSimulator; - this.privateNonceProvider = privateNonceProvider; - this.privateWorldStateReader = privateWorldStateReader; - this.privateStateRootResolver = privacyParameters.getPrivateStateRootResolver(); + super( + blockchain, + privacyParameters, + chainId, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader); this.privacyPluginService = privacyParameters.getPrivacyService(); } @@ -78,16 +63,6 @@ public class PluginPrivacyController implements PrivacyController { .toBase64String(); } - @Override - public ValidationResult validatePrivateTransaction( - final PrivateTransaction privateTransaction, final String privacyUserId) { - final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); - return privateTransactionValidator.validate( - privateTransaction, - determineBesuNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), - true); - } - @Override public Optional findPrivateTransactionByPmtHash( final Hash pmtHash, final String enclaveKey) { @@ -130,12 +105,6 @@ public class PluginPrivacyController implements PrivacyController { return Optional.of(executedPrivateTransaction); } - @Override - public ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin"); - } - @Override public PrivacyGroup createPrivacyGroup( final List addresses, @@ -152,101 +121,6 @@ public class PluginPrivacyController implements PrivacyController { "Method not supported when using PrivacyPlugin"); } - @Override - public PrivacyGroup[] findOffchainPrivacyGroupByMembers( - final List addresses, final String privacyUserId) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin"); - } - - @Override - public long determineEeaNonce( - final String privateFrom, - final String[] privateFor, - final Address address, - final String privacyUserId) { - - final String privacyGroupId = createPrivacyGroupId(privateFrom, privateFor); - - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); - - return determineBesuNonce(address, privacyGroupId, privacyUserId); - } - - private String createPrivacyGroupId(final String privateFrom, final String[] privateFor) { - final Bytes32 privacyGroupId = - PrivacyGroupUtil.calculateEeaPrivacyGroupId( - Bytes.fromBase64String(privateFrom), - Arrays.stream(privateFor).map(Bytes::fromBase64String).collect(Collectors.toList())); - - return privacyGroupId.toBase64String(); - } - - @Override - public long determineBesuNonce( - final Address sender, final String privacyGroupId, final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); - - return privateNonceProvider.getNonce( - sender, Bytes32.wrap(Bytes.fromBase64String(privacyGroupId))); - } - - @Override - public Optional simulatePrivateTransaction( - final String privacyGroupId, - final String privacyUserId, - final CallParameter callParams, - final long blockNumber) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); - - return privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber); - } - - @Override - public Optional getContractCode( - final String privacyGroupId, - final Address contractAddress, - final Hash blockHash, - final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); - - return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress); - } - - @Override - public PrivateTransactionSimulator getTransactionSimulator() { - return privateTransactionSimulator; - } - - @Override - public Optional getStateRootByBlockNumber( - final String privacyGroupId, final String privacyUserId, final long blockNumber) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); - - return blockchain - .getBlockByNumber(blockNumber) - .map( - block -> - privateStateRootResolver.resolveLastStateRoot( - Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)), block.getHash())); - } - - @Override - public Optional buildAndSendAddPayload( - final PrivateTransaction privateTransaction, - final Bytes32 privacyGroupId, - final String privacyUserId) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin - you can not send a payload without it being on-chain"); - } - - @Override - public Optional findOffchainPrivacyGroupByGroupId( - final String toBase64String, final String privacyUserId) { - - return findPrivacyGroupByGroupId(toBase64String, privacyUserId); - } - @Override public Optional findPrivacyGroupByGroupId( final String privacyGroupId, final String privacyUserId) { @@ -262,33 +136,12 @@ public class PluginPrivacyController implements PrivacyController { } @Override - public List findOnchainPrivacyGroupByMembers( + public PrivacyGroup[] findPrivacyGroupByMembers( final List asList, final String privacyUserId) { throw new PrivacyConfigurationNotSupportedException( "Method not supported when using PrivacyPlugin"); } - @Override - public Optional findOnchainPrivacyGroupAndAddNewMembers( - final Bytes privacyGroupId, - final String privacyUserId, - final PrivateTransaction privateTransaction) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin"); - } - - @Override - public List retrieveAddBlob(final String addDataKey) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin"); - } - - @Override - public boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { - throw new PrivacyConfigurationNotSupportedException( - "Method not supported when using PrivacyPlugin"); - } - @Override public void verifyPrivacyGroupContainsPrivacyUserId( final String privacyGroupId, final String privacyUserId, final Optional blockNumber) { @@ -296,7 +149,7 @@ public class PluginPrivacyController implements PrivacyController { .getPrivacyGroupAuthProvider() .canAccess(privacyGroupId, privacyUserId, blockNumber)) { throw new MultiTenancyValidationException( - "PrivacyUserId " + privacyUserId + " does not have access to " + privacyGroupId); + "Privacy group must contain the enclave public key"); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java index 2bfe88d2d..8f0d3f127 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java @@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.privacy; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; @@ -27,7 +26,6 @@ import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; public interface PrivacyController { @@ -39,22 +37,19 @@ public interface PrivacyController { String privacyUserId, Optional privacyGroup); - ReceiveResponse retrieveTransaction(String enclaveKey, String privacyUserId); - PrivacyGroup createPrivacyGroup( List addresses, String name, String description, String privacyUserId); String deletePrivacyGroup(String privacyGroupId, String privacyUserId); - PrivacyGroup[] findOffchainPrivacyGroupByMembers(List addresses, String privacyUserId); + PrivacyGroup[] findPrivacyGroupByMembers(List addresses, String privacyUserId); + + Optional findPrivacyGroupByGroupId(String privacyGroupId, String privacyUserId); ValidationResult validatePrivateTransaction( PrivateTransaction privateTransaction, String privacyUserId); - long determineEeaNonce( - String privateFrom, String[] privateFor, Address address, String privacyUserId); - - long determineBesuNonce(Address sender, String privacyGroupId, String privacyUserId); + long determineNonce(Address sender, String privacyGroupId, String privacyUserId); Optional simulatePrivateTransaction( final String privacyGroupId, @@ -62,30 +57,12 @@ public interface PrivacyController { final CallParameter callParams, final long blockNumber); - Optional buildAndSendAddPayload( - PrivateTransaction privateTransaction, Bytes32 privacyGroupId, String privacyUserId); - - Optional findOffchainPrivacyGroupByGroupId( - String privacyGroupId, String privacyUserId); - - Optional findPrivacyGroupByGroupId( - final String privacyGroupId, final String privacyUserId); - - List findOnchainPrivacyGroupByMembers(List asList, String privacyUserId); - Optional getContractCode( final String privacyGroupId, final Address contractAddress, final Hash blockHash, final String privacyUserId); - Optional findOnchainPrivacyGroupAndAddNewMembers( - Bytes privacyGroupId, String privacyUserId, final PrivateTransaction privateTransaction); - - List retrieveAddBlob(String addDataKey); - - boolean isGroupAdditionTransaction(PrivateTransaction privateTransaction); - void verifyPrivacyGroupContainsPrivacyUserId( final String privacyGroupId, final String privacyUserId) throws MultiTenancyValidationException; @@ -94,8 +71,6 @@ public interface PrivacyController { final String privacyGroupId, final String privacyUserId, final Optional blockNumber) throws MultiTenancyValidationException; - PrivateTransactionSimulator getTransactionSimulator(); - Optional getStateRootByBlockNumber( final String privacyGroupId, final String privacyUserId, final long blockNumber); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java index aa09e0453..94610b985 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java @@ -15,14 +15,12 @@ package org.hyperledger.besu.ethereum.privacy; import org.hyperledger.besu.crypto.Hash; -import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.apache.tuweni.bytes.Bytes; @@ -36,12 +34,12 @@ public class PrivacyGroupUtil { // https://github.com/ConsenSys/orion/blob/05759341ec1a216e6837df91e421207c8294ad2a/src/main/java/net/consensys/orion/enclave/sodium/SodiumEnclave.java public static Bytes32 calculateEeaPrivacyGroupId( final Bytes privateFrom, final List privateFor) { - final List privacyGroupIds = new ArrayList<>(); - privacyGroupIds.add(privateFrom); - privacyGroupIds.addAll(privateFor); + final List privacyGroupMembers = new ArrayList<>(); + privacyGroupMembers.add(privateFrom); + privacyGroupMembers.addAll(privateFor); final List sortedPublicEnclaveKeys = - privacyGroupIds.stream() + privacyGroupMembers.stream() .distinct() .map(Bytes::toArray) .sorted(Comparator.comparing(Arrays::hashCode)) @@ -54,25 +52,4 @@ public class PrivacyGroupUtil { return Hash.keccak256(bytesValueRLPOutput.encoded()); } - - public static Optional findOnchainPrivacyGroup( - final PrivacyController privacyController, - final Optional maybePrivacyGroupId, - final String privacyUserId, - final PrivateTransaction privateTransaction) { - return maybePrivacyGroupId.flatMap( - privacyGroupId -> - privacyController.findOnchainPrivacyGroupAndAddNewMembers( - privacyGroupId, privacyUserId, privateTransaction)); - } - - public static Optional findOffchainPrivacyGroup( - final PrivacyController privacyController, - final Optional maybePrivacyGroupId, - final String privacyUserId) { - return maybePrivacyGroupId.flatMap( - privacyGroupId -> - privacyController.findOffchainPrivacyGroupByGroupId( - privacyGroupId.toBase64String(), privacyUserId)); - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java index f83abf231..8a1dd7dc1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java @@ -161,7 +161,8 @@ public class PrivateTransactionProcessor { .build(); } else { final Address to = transaction.getTo().get(); - final Optional maybeContract = Optional.ofNullable(privateWorldState.get(to)); + final Optional maybeContract = + Optional.ofNullable(mutablePrivateWorldStateUpdater.get(to)); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.MESSAGE_CALL) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyController.java index 97810341e..17cf2903a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyController.java @@ -14,64 +14,28 @@ */ package org.hyperledger.besu.ethereum.privacy; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY_PROXY; -import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE; -import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.GET_PARTICIPANTS_METHOD_SIGNATURE; -import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.GET_VERSION_METHOD_SIGNATURE; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.enclave.Enclave; -import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.enclave.types.SendResponse; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; -import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; -import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.transaction.CallParameter; -import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -public class RestrictedDefaultPrivacyController implements PrivacyController { +public class RestrictedDefaultPrivacyController extends AbstractRestrictedPrivacyController { private static final Logger LOG = LogManager.getLogger(); - private final Blockchain blockchain; - private final PrivateStateStorage privateStateStorage; - private final Enclave enclave; - private final PrivateTransactionValidator privateTransactionValidator; - private final PrivateTransactionSimulator privateTransactionSimulator; - private final PrivateNonceProvider privateNonceProvider; - private final PrivateWorldStateReader privateWorldStateReader; - private final PrivateTransactionLocator privateTransactionLocator; - private final PrivateStateRootResolver privateStateRootResolver; - public RestrictedDefaultPrivacyController( final Blockchain blockchain, final PrivacyParameters privacyParameters, @@ -99,22 +63,15 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { final PrivateNonceProvider privateNonceProvider, final PrivateWorldStateReader privateWorldStateReader, final PrivateStateRootResolver privateStateRootResolver) { - this.blockchain = blockchain; - this.privateStateStorage = privateStateStorage; - this.enclave = enclave; - this.privateTransactionValidator = privateTransactionValidator; - this.privateTransactionSimulator = privateTransactionSimulator; - this.privateNonceProvider = privateNonceProvider; - this.privateWorldStateReader = privateWorldStateReader; - this.privateTransactionLocator = - new PrivateTransactionLocator(blockchain, enclave, privateStateStorage); - this.privateStateRootResolver = privateStateRootResolver; - } - - @Override - public Optional findPrivateTransactionByPmtHash( - final Hash pmtHash, final String enclaveKey) { - return privateTransactionLocator.findByPmtHash(pmtHash, enclaveKey); + super( + blockchain, + privateStateStorage, + enclave, + privateTransactionValidator, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privateStateRootResolver); } @Override @@ -133,11 +90,6 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { } } - @Override - public ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { - return enclave.receive(enclaveKey, privacyUserId); - } - @Override public PrivacyGroup createPrivacyGroup( final List addresses, @@ -153,319 +105,17 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { } @Override - public PrivacyGroup[] findOffchainPrivacyGroupByMembers( + public PrivacyGroup[] findPrivacyGroupByMembers( final List addresses, final String privacyUserId) { return enclave.findPrivacyGroup(addresses); } - @Override - public ValidationResult validatePrivateTransaction( - final PrivateTransaction privateTransaction, final String privacyUserId) { - final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); - return privateTransactionValidator.validate( - privateTransaction, - determineBesuNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), - true); - } - - @Override - public long determineEeaNonce( - final String privateFrom, - final String[] privateFor, - final Address address, - final String privacyUserId) { - final List groupMembers = Lists.asList(privateFrom, privateFor); - - final List matchingGroups = - Lists.newArrayList(enclave.findPrivacyGroup(groupMembers)); - - final List legacyGroups = - matchingGroups.stream() - .filter(group -> group.getType() == PrivacyGroup.Type.LEGACY) - .collect(Collectors.toList()); - - if (legacyGroups.size() == 0) { - // the legacy group does not exist yet - return 0; - } - Preconditions.checkArgument( - legacyGroups.size() == 1, - String.format( - "Found invalid number of privacy groups (%d), expected 1.", legacyGroups.size())); - - final String privacyGroupId = legacyGroups.get(0).getPrivacyGroupId(); - - return determineBesuNonce(address, privacyGroupId, privacyUserId); - } - - @Override - public long determineBesuNonce( - final Address sender, final String privacyGroupId, final String privacyUserId) { - return privateNonceProvider.getNonce( - sender, Bytes32.wrap(Bytes.fromBase64String(privacyGroupId))); - } - - @Override - public Optional simulatePrivateTransaction( - final String privacyGroupId, - final String privacyUserId, - final CallParameter callParams, - final long blockNumber) { - final Optional result = - privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber); - return result; - } - - @Override - public Optional buildAndSendAddPayload( - final PrivateTransaction privateTransaction, - final Bytes32 privacyGroupId, - final String privacyUserId) { - if (isGroupAdditionTransaction(privateTransaction)) { - final List privateTransactionMetadataList = - buildTransactionMetadataList(privacyGroupId); - if (privateTransactionMetadataList.size() > 0) { - final List privateTransactionWithMetadataList = - retrievePrivateTransactions( - privacyGroupId, privateTransactionMetadataList, privacyUserId); - final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList); - final List privateFor = - getParticipantsFromParameter(privateTransaction.getPayload()); - return Optional.of( - enclave.send(bytes.toBase64String(), privacyUserId, privateFor).getKey()); - } - } - return Optional.empty(); - } - @Override public Optional findPrivacyGroupByGroupId( final String privacyGroupId, final String privacyUserId) { - try { - return findOffchainPrivacyGroupByGroupId(privacyGroupId, privacyUserId); - } catch (final EnclaveClientException ex) { - // An exception is thrown if the offchain group cannot be found - LOG.debug("Offchain privacy group not found: {}", privacyGroupId); - } - return findOnchainPrivacyGroupByGroupId(Bytes.fromBase64String(privacyGroupId), privacyUserId); - } - - @Override - public Optional findOffchainPrivacyGroupByGroupId( - final String privacyGroupId, final String privacyUserId) { return Optional.ofNullable(enclave.retrievePrivacyGroup(privacyGroupId)); } - @Override - public List findOnchainPrivacyGroupByMembers( - final List addresses, final String privacyUserId) { - final ArrayList privacyGroups = new ArrayList<>(); - final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = - privateStateStorage - .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) - .orElse(PrivacyGroupHeadBlockMap.empty()); - privacyGroupHeadBlockMap - .keySet() - .forEach( - c -> { - final Optional maybePrivacyGroup = - findOnchainPrivacyGroupByGroupId(c, privacyUserId); - if (maybePrivacyGroup.isPresent() - && maybePrivacyGroup.get().getMembers().containsAll(addresses)) { - privacyGroups.add(maybePrivacyGroup.get()); - } - }); - return privacyGroups; - } - - public Optional findOnchainPrivacyGroupByGroupId( - final Bytes privacyGroupId, final String enclaveKey) { - // get the privateFor list from the management contract - final Optional privateTransactionSimulatorResultOptional = - privateTransactionSimulator.process( - privacyGroupId.toBase64String(), buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE)); - - if (privateTransactionSimulatorResultOptional.isPresent() - && privateTransactionSimulatorResultOptional.get().isSuccessful()) { - final RLPInput rlpInput = - RLP.input(privateTransactionSimulatorResultOptional.get().getOutput()); - if (rlpInput.nextSize() > 0) { - return Optional.of( - new PrivacyGroup( - privacyGroupId.toBase64String(), - PrivacyGroup.Type.ONCHAIN, - "", - "", - decodeList(rlpInput.raw()))); - } else { - return Optional.empty(); - } - } else { - return Optional.empty(); - } - } - - @Override - public Optional findOnchainPrivacyGroupAndAddNewMembers( - final Bytes privacyGroupId, - final String privacyUserId, - final PrivateTransaction privateTransaction) { - // get the privateFor list from the management contract - final Optional privateTransactionSimulatorResultOptional = - privateTransactionSimulator.process( - privacyGroupId.toBase64String(), buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE)); - - final List members = new ArrayList<>(); - if (privateTransactionSimulatorResultOptional.isPresent() - && privateTransactionSimulatorResultOptional.get().isSuccessful()) { - final RLPInput rlpInput = - RLP.input(privateTransactionSimulatorResultOptional.get().getOutput()); - if (rlpInput.nextSize() > 0) { - members.addAll(decodeList(rlpInput.raw())); - if (!members.contains(privacyUserId)) { - return Optional.empty(); - } - } - } - if (isGroupAdditionTransaction(privateTransaction)) { - final List participantsFromParameter = - getParticipantsFromParameter(privateTransaction.getPayload()); - members.addAll(participantsFromParameter); - } - if (members.isEmpty()) { - return Optional.empty(); - } else { - return Optional.of( - new PrivacyGroup( - privacyGroupId.toBase64String(), PrivacyGroup.Type.ONCHAIN, "", "", members)); - } - } - - private List decodeList(final Bytes rlpEncodedList) { - final ArrayList decodedElements = new ArrayList<>(); - // first 32 bytes is dynamic list offset - final UInt256 lengthOfList = UInt256.fromBytes(rlpEncodedList.slice(32, 32)); // length of list - for (int i = 0; i < lengthOfList.toLong(); ++i) { - decodedElements.add( - Bytes.wrap(rlpEncodedList.slice(64 + (32 * i), 32)).toBase64String()); // participant - } - return decodedElements; - } - - private List getParticipantsFromParameter(final Bytes input) { - final List participants = new ArrayList<>(); - final Bytes mungedParticipants = input.slice(4 + 32 + 32); - for (int i = 0; i <= mungedParticipants.size() - 32; i += 32) { - participants.add(mungedParticipants.slice(i, 32).toBase64String()); - } - return participants; - } - - private CallParameter buildCallParams(final Bytes methodCall) { - return new CallParameter( - Address.ZERO, ONCHAIN_PRIVACY_PROXY, 3000000, Wei.of(1000), Wei.ZERO, methodCall); - } - - private List buildTransactionMetadataList( - final Bytes privacyGroupId) { - final List pmtHashes = new ArrayList<>(); - PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = - privateStateStorage - .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) - .orElse(PrivacyGroupHeadBlockMap.empty()); - if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) { - Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); - while (blockHash != null) { - pmtHashes.addAll( - 0, - privateStateStorage - .getPrivateBlockMetadata(blockHash, Bytes32.wrap(privacyGroupId)) - .get() - .getPrivateTransactionMetadataList()); - blockHash = blockchain.getBlockHeader(blockHash).get().getParentHash(); - privacyGroupHeadBlockMap = - privateStateStorage - .getPrivacyGroupHeadBlockMap(blockHash) - .orElse(PrivacyGroupHeadBlockMap.empty()); - if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) { - blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); - } else { - break; - } - } - } - return pmtHashes; - } - - private List retrievePrivateTransactions( - final Bytes32 privacyGroupId, - final List privateTransactionMetadataList, - final String privacyUserId) { - final ArrayList privateTransactions = new ArrayList<>(); - privateStateStorage - .getAddDataKey(privacyGroupId) - .ifPresent(key -> privateTransactions.addAll(retrieveAddBlob(key.toBase64String()))); - for (int i = privateTransactions.size(); i < privateTransactionMetadataList.size(); i++) { - final PrivateTransactionMetadata privateTransactionMetadata = - privateTransactionMetadataList.get(i); - final Transaction privateMarkerTransaction = - blockchain - .getTransactionByHash(privateTransactionMetadata.getPrivateMarkerTransactionHash()) - .orElseThrow(); - final ReceiveResponse receiveResponse = - retrieveTransaction( - privateMarkerTransaction.getPayload().slice(0, 32).toBase64String(), privacyUserId); - final BytesValueRLPInput input = - new BytesValueRLPInput( - Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); - input.enterList(); - privateTransactions.add( - new PrivateTransactionWithMetadata( - PrivateTransaction.readFrom(input), privateTransactionMetadata)); - input.leaveListLenient(); - } - - return privateTransactions; - } - - @Override - public boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { - return privateTransaction.getTo().isPresent() - && privateTransaction.getTo().get().equals(ONCHAIN_PRIVACY_PROXY) - && privateTransaction - .getPayload() - .toHexString() - .startsWith(ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString()); - } - - @Override - public Optional getContractCode( - final String privacyGroupId, - final Address contractAddress, - final Hash blockHash, - final String privacyUserId) { - return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress); - } - - @Override - public List retrieveAddBlob(final String addDataKey) { - final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey); - return PrivateTransactionWithMetadata.readListFromPayload( - Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload()))); - } - - private Bytes serializeAddToGroupPayload( - final List privateTransactionWithMetadataList) { - - final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); - rlpOutput.startList(); - privateTransactionWithMetadataList.forEach( - privateTransactionWithMetadata -> privateTransactionWithMetadata.writeTo(rlpOutput)); - rlpOutput.endList(); - - return rlpOutput.encoded(); - } - private SendResponse sendRequest( final PrivateTransaction privateTransaction, final String privacyUserId, @@ -474,19 +124,7 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { if (maybePrivacyGroup.isPresent()) { final PrivacyGroup privacyGroup = maybePrivacyGroup.get(); - if (privacyGroup.getType() == PrivacyGroup.Type.ONCHAIN) { - // onchain privacy group - final Optional result = - privateTransactionSimulator.process( - privateTransaction.getPrivacyGroupId().get().toBase64String(), - buildCallParams(GET_VERSION_METHOD_SIGNATURE)); - new VersionedPrivateTransaction(privateTransaction, result).writeTo(rlpOutput); - final List onchainPrivateFor = privacyGroup.getMembers(); - return enclave.send( - rlpOutput.encoded().toBase64String(), - privateTransaction.getPrivateFrom().toBase64String(), - onchainPrivateFor); - } else if (privacyGroup.getType() == PrivacyGroup.Type.PANTHEON) { + if (privacyGroup.getType() == PrivacyGroup.Type.PANTHEON) { // offchain privacy group privateTransaction.writeTo(rlpOutput); return enclave.send( @@ -495,7 +133,12 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { privateTransaction.getPrivacyGroupId().get().toBase64String()); } else { // this should not happen - throw new RuntimeException(); + throw new IllegalArgumentException( + "Wrong privacy group type " + + privacyGroup.getType() + + " when " + + PrivacyGroup.Type.PANTHEON + + "was expected."); } } // legacy transaction @@ -524,29 +167,16 @@ public class RestrictedDefaultPrivacyController implements PrivacyController { @Override public void verifyPrivacyGroupContainsPrivacyUserId( final String privacyGroupId, final String privacyUserId) { - // NO VALIDATION NEEDED + final PrivacyGroup offchainPrivacyGroup = enclave.retrievePrivacyGroup(privacyGroupId); + if (!offchainPrivacyGroup.getMembers().contains(privacyUserId)) { + throw new MultiTenancyValidationException( + "Privacy group must contain the enclave public key"); + } } @Override public void verifyPrivacyGroupContainsPrivacyUserId( - final String privacyGroupId, final String privacyUserId, final Optional blockNumber) - throws MultiTenancyValidationException { - // NO VALIDATION NEEDED - } - - @Override - public PrivateTransactionSimulator getTransactionSimulator() { - return privateTransactionSimulator; - } - - @Override - public Optional getStateRootByBlockNumber( - final String privacyGroupId, final String privacyUserId, final long blockNumber) { - return blockchain - .getBlockByNumber(blockNumber) - .map( - block -> - privateStateRootResolver.resolveLastStateRoot( - Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)), block.getHash())); + final String privacyGroupId, final String privacyUserId, final Optional blockNumber) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyController.java deleted file mode 100644 index 45fa44480..000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyController.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.enclave.Enclave; -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.transaction.CallParameter; -import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -public class RestrictedMultiTenancyPrivacyController implements PrivacyController { - - private final PrivacyController privacyController; - private final Enclave enclave; - private final PrivateTransactionValidator privateTransactionValidator; - private final Optional onchainPrivacyGroupContract; - - public RestrictedMultiTenancyPrivacyController( - final PrivacyController privacyController, - final Optional chainId, - final Enclave enclave, - final boolean onchainPrivacyGroupsEnabled) { - this.privacyController = privacyController; - this.enclave = enclave; - this.onchainPrivacyGroupContract = - onchainPrivacyGroupsEnabled - ? Optional.of( - new OnchainPrivacyGroupContract(privacyController.getTransactionSimulator())) - : Optional.empty(); - privateTransactionValidator = new PrivateTransactionValidator(chainId); - } - - @VisibleForTesting - RestrictedMultiTenancyPrivacyController( - final PrivacyController privacyController, - final Optional chainId, - final Enclave enclave, - final Optional onchainPrivacyGroupContract) { - this.privacyController = privacyController; - this.enclave = enclave; - this.onchainPrivacyGroupContract = onchainPrivacyGroupContract; - privateTransactionValidator = new PrivateTransactionValidator(chainId); - } - - @Override - public Optional findPrivateTransactionByPmtHash( - final Hash pmtHash, final String enclaveKey) { - return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey); - } - - @Override - public String createPrivateMarkerTransactionPayload( - final PrivateTransaction privateTransaction, - final String privacyUserId, - final Optional maybePrivacyGroup) { - verifyPrivateFromMatchesPrivacyUserId( - privateTransaction.getPrivateFrom().toBase64String(), privacyUserId); - if (privateTransaction.getPrivacyGroupId().isPresent()) { - verifyPrivacyGroupContainsPrivacyUserId( - privateTransaction.getPrivacyGroupId().get().toBase64String(), privacyUserId); - } - return privacyController.createPrivateMarkerTransactionPayload( - privateTransaction, privacyUserId, maybePrivacyGroup); - } - - @Override - public ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { - // no validation necessary as the enclave receive only returns data for the enclave public key - return privacyController.retrieveTransaction(enclaveKey, privacyUserId); - } - - @Override - public PrivacyGroup createPrivacyGroup( - final List addresses, - final String name, - final String description, - final String privacyUserId) { - // no validation necessary as the enclave createPrivacyGroup fails if the addresses don't - // include the from (privacyUserId) - return privacyController.createPrivacyGroup(addresses, name, description, privacyUserId); - } - - @Override - public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); - return privacyController.deletePrivacyGroup(privacyGroupId, privacyUserId); - } - - @Override - public PrivacyGroup[] findOffchainPrivacyGroupByMembers( - final List addresses, final String privacyUserId) { - if (!addresses.contains(privacyUserId)) { - throw new MultiTenancyValidationException( - "Privacy group addresses must contain the enclave public key"); - } - final PrivacyGroup[] resultantGroups = - privacyController.findOffchainPrivacyGroupByMembers(addresses, privacyUserId); - return Arrays.stream(resultantGroups) - .filter(g -> g.getMembers().contains(privacyUserId)) - .toArray(PrivacyGroup[]::new); - } - - @Override - public ValidationResult validatePrivateTransaction( - final PrivateTransaction privateTransaction, final String privacyUserId) { - - final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); - verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); - return privateTransactionValidator.validate( - privateTransaction, - determineBesuNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), - true); - } - - @Override - public long determineEeaNonce( - final String privateFrom, - final String[] privateFor, - final Address address, - final String privacyUserId) { - verifyPrivateFromMatchesPrivacyUserId(privateFrom, privacyUserId); - return privacyController.determineEeaNonce(privateFrom, privateFor, address, privacyUserId); - } - - @Override - public long determineBesuNonce( - final Address sender, final String privacyGroupId, final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); - return privacyController.determineBesuNonce(sender, privacyGroupId, privacyUserId); - } - - @Override - public Optional simulatePrivateTransaction( - final String privacyGroupId, - final String privacyUserId, - final CallParameter callParams, - final long blockNumber) { - verifyPrivacyGroupContainsPrivacyUserId( - privacyGroupId, privacyUserId, Optional.of(blockNumber)); - return privacyController.simulatePrivateTransaction( - privacyGroupId, privacyUserId, callParams, blockNumber); - } - - @Override - public Optional buildAndSendAddPayload( - final PrivateTransaction privateTransaction, - final Bytes32 privacyGroupId, - final String privacyUserId) { - verifyPrivateFromMatchesPrivacyUserId( - privateTransaction.getPrivateFrom().toBase64String(), privacyUserId); - verifyPrivacyGroupContainsPrivacyUserId( - privateTransaction.getPrivacyGroupId().get().toBase64String(), privacyUserId); - return privacyController.buildAndSendAddPayload( - privateTransaction, privacyGroupId, privacyUserId); - } - - @Override - public Optional findOffchainPrivacyGroupByGroupId( - final String privacyGroupId, final String privacyUserId) { - final Optional maybePrivacyGroup = - privacyController.findOffchainPrivacyGroupByGroupId(privacyGroupId, privacyUserId); - checkGroupParticipation(maybePrivacyGroup, privacyUserId); - return maybePrivacyGroup; - } - - @Override - public Optional findPrivacyGroupByGroupId( - final String privacyGroupId, final String privacyUserId) { - final Optional maybePrivacyGroup = - privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId); - checkGroupParticipation(maybePrivacyGroup, privacyUserId); - return maybePrivacyGroup; - } - - private void checkGroupParticipation( - final Optional maybePrivacyGroup, final String enclaveKey) { - if (maybePrivacyGroup.isPresent() - && !maybePrivacyGroup.get().getMembers().contains(enclaveKey)) { - throw new MultiTenancyValidationException( - "Privacy group must contain the enclave public key"); - } - } - - @Override - public List findOnchainPrivacyGroupByMembers( - final List addresses, final String privacyUserId) { - if (!addresses.contains(privacyUserId)) { - throw new MultiTenancyValidationException( - "Privacy group addresses must contain the enclave public key"); - } - final List resultantGroups = - privacyController.findOnchainPrivacyGroupByMembers(addresses, privacyUserId); - return resultantGroups.stream() - .filter(g -> g.getMembers().contains(privacyUserId)) - .collect(Collectors.toList()); - } - - @Override - public List retrieveAddBlob(final String addDataKey) { - return privacyController.retrieveAddBlob(addDataKey); - } - - @Override - public boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { - return privacyController.isGroupAdditionTransaction(privateTransaction); - } - - @Override - public Optional getContractCode( - final String privacyGroupId, - final Address contractAddress, - final Hash blockHash, - final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); - return privacyController.getContractCode( - privacyGroupId, contractAddress, blockHash, privacyUserId); - } - - @Override - public Optional findOnchainPrivacyGroupAndAddNewMembers( - final Bytes privacyGroupId, - final String privacyUserId, - final PrivateTransaction privateTransaction) { - final Optional maybePrivacyGroup = - privacyController.findOnchainPrivacyGroupAndAddNewMembers( - privacyGroupId, privacyUserId, privateTransaction); - // The check that the privacyUserId is a member (if the group already exists) is done in the - // DefaultPrivacyController. - return maybePrivacyGroup; - } - - private void verifyPrivateFromMatchesPrivacyUserId( - final String privateFrom, final String privacyUserId) { - if (!privateFrom.equals(privacyUserId)) { - throw new MultiTenancyValidationException( - "Transaction privateFrom must match enclave public key"); - } - } - - @Override - public void verifyPrivacyGroupContainsPrivacyUserId( - final String privacyGroupId, final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId, Optional.empty()); - } - - @Override - public void verifyPrivacyGroupContainsPrivacyUserId( - final String privacyGroupId, final String privacyUserId, final Optional blockNumber) - throws MultiTenancyValidationException { - onchainPrivacyGroupContract.ifPresentOrElse( - (contract) -> { - verifyOnchainPrivacyGroupContainsMember( - contract, privacyGroupId, privacyUserId, blockNumber); - }, - () -> { - verifyOffchainPrivacyGroupContainsMember(privacyGroupId, privacyUserId); - }); - } - - private void verifyOffchainPrivacyGroupContainsMember( - final String privacyGroupId, final String privacyUserId) { - final PrivacyGroup offchainPrivacyGroup = enclave.retrievePrivacyGroup(privacyGroupId); - if (!offchainPrivacyGroup.getMembers().contains(privacyUserId)) { - throw new MultiTenancyValidationException( - "Privacy group must contain the enclave public key"); - } - } - - private void verifyOnchainPrivacyGroupContainsMember( - final OnchainPrivacyGroupContract contract, - final String privacyGroupId, - final String privacyUserId, - final Optional blockNumber) { - final Optional maybePrivacyGroup = - contract.getPrivacyGroupByIdAndBlockNumber(privacyGroupId, blockNumber); - // IF the group exists, check member - // ELSE member is valid if the group doesn't exist yet - this is normal for onchain privacy - // groups - maybePrivacyGroup.ifPresent( - (group) -> { - if (!group.getMembers().contains(privacyUserId)) { - throw new MultiTenancyValidationException( - "Privacy group must contain the enclave public key"); - } - }); - } - - @Override - public PrivateTransactionSimulator getTransactionSimulator() { - return privacyController.getTransactionSimulator(); - } - - @Override - public Optional getStateRootByBlockNumber( - final String privacyGroupId, final String privacyUserId, final long blockNumber) { - verifyPrivacyGroupContainsPrivacyUserId( - privacyGroupId, privacyUserId, Optional.of(blockNumber)); - return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerOnchainTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerOnchainTest.java similarity index 74% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerOnchainTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerOnchainTest.java index 6ee5a3914..d34616b76 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerOnchainTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerOnchainTest.java @@ -16,21 +16,18 @@ package org.hyperledger.besu.ethereum.privacy; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.enclave.Enclave; -import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.evm.log.Log; -import java.math.BigInteger; import java.util.ArrayList; -import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; @@ -40,34 +37,20 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class RestrictedMultiTenancyPrivacyControllerOnchainTest { +public class MultiTenancyPrivacyControllerOnchainTest { private static final String ENCLAVE_PUBLIC_KEY1 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; private static final String ENCLAVE_PUBLIC_KEY2 = "OnviftjiizpjRt+HTuFBsKo2bVqD+nNlNYL5EE7y3Id="; private static final String PRIVACY_GROUP_ID = "nNlNYL5EE7y3IdM="; private static final ArrayList LOGS = new ArrayList<>(); - private static final PrivacyGroup ONCHAIN_PRIVACY_GROUP = - new PrivacyGroup("", PrivacyGroup.Type.ONCHAIN, "", "", List.of(ENCLAVE_PUBLIC_KEY1)); private final PrivacyController privacyController = mock(PrivacyController.class); - private final Enclave enclave = mock(Enclave.class); - private final OnchainPrivacyGroupContract onchainPrivacyGroupContract = - mock(OnchainPrivacyGroupContract.class); - private RestrictedMultiTenancyPrivacyController multiTenancyPrivacyController; + private MultiTenancyPrivacyController multiTenancyPrivacyController; @Before public void setup() { - when(onchainPrivacyGroupContract.getPrivacyGroupByIdAndBlockNumber( - PRIVACY_GROUP_ID, Optional.of(1L))) - .thenReturn(Optional.of(ONCHAIN_PRIVACY_GROUP)); - - multiTenancyPrivacyController = - new RestrictedMultiTenancyPrivacyController( - privacyController, - Optional.of(BigInteger.valueOf(2018)), - enclave, - Optional.of(onchainPrivacyGroupContract)); + multiTenancyPrivacyController = new MultiTenancyPrivacyController(privacyController); } @Test @@ -90,6 +73,12 @@ public class RestrictedMultiTenancyPrivacyControllerOnchainTest { @Test(expected = MultiTenancyValidationException.class) public void simulatePrivateTransactionFailsForAbsentEnclaveKey() { + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId( + PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2, Optional.of(1L)); multiTenancyPrivacyController.simulatePrivateTransaction( PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java similarity index 61% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java index d742ebf1c..c6174ddec 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java @@ -17,7 +17,7 @@ package org.hyperledger.besu.ethereum.privacy; 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.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -25,15 +25,12 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.evm.log.Log; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,7 +44,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class RestrictedMultiTenancyPrivacyControllerTest { +public class MultiTenancyPrivacyControllerTest { private static final String ENCLAVE_PUBLIC_KEY1 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; private static final String ENCLAVE_PUBLIC_KEY2 = "OnviftjiizpjRt+HTuFBsKo2bVqD+nNlNYL5EE7y3Id="; @@ -56,20 +53,14 @@ public class RestrictedMultiTenancyPrivacyControllerTest { private static final ArrayList LOGS = new ArrayList<>(); private static final PrivacyGroup PANTHEON_PRIVACY_GROUP = new PrivacyGroup("", PrivacyGroup.Type.PANTHEON, "", "", Collections.emptyList()); - private static final PrivacyGroup PANTHEON_GROUP_WITH_ENCLAVE_KEY_1 = - new PrivacyGroup( - PRIVACY_GROUP_ID, PrivacyGroup.Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY1)); @Mock private PrivacyController privacyController; - @Mock private Enclave enclave; - private RestrictedMultiTenancyPrivacyController multiTenancyPrivacyController; + private MultiTenancyPrivacyController multiTenancyPrivacyController; @Before public void setup() { - multiTenancyPrivacyController = - new RestrictedMultiTenancyPrivacyController( - privacyController, Optional.of(BigInteger.valueOf(2018)), enclave, false); + multiTenancyPrivacyController = new MultiTenancyPrivacyController(privacyController); } @Test @@ -111,7 +102,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { when(privacyController.createPrivateMarkerTransactionPayload( transaction, ENCLAVE_PUBLIC_KEY1, Optional.of(privacyGroupWithPrivacyUserId))) .thenReturn(ENCLAVE_KEY); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)).thenReturn(privacyGroupWithPrivacyUserId); final String response = multiTenancyPrivacyController.createPrivateMarkerTransactionPayload( @@ -120,45 +110,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { verify(privacyController) .createPrivateMarkerTransactionPayload( transaction, ENCLAVE_PUBLIC_KEY1, Optional.of(privacyGroupWithPrivacyUserId)); - verify(enclave).retrievePrivacyGroup(PRIVACY_GROUP_ID); - } - - @Test - public void sendEeaTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() { - final PrivateTransaction transaction = - PrivateTransaction.builder() - .privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2)) - .build(); - - assertThatThrownBy( - () -> - multiTenancyPrivacyController.createPrivateMarkerTransactionPayload( - transaction, ENCLAVE_PUBLIC_KEY1, Optional.empty())) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessage("Transaction privateFrom must match enclave public key"); - - verify(privacyController, never()) - .createPrivateMarkerTransactionPayload(transaction, ENCLAVE_PUBLIC_KEY1, Optional.empty()); - } - - @Test - public void sendBesuTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() { - final PrivateTransaction transaction = - PrivateTransaction.builder() - .privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2)) - .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) - .build(); - - assertThatThrownBy( - () -> - multiTenancyPrivacyController.createPrivateMarkerTransactionPayload( - transaction, ENCLAVE_PUBLIC_KEY1, Optional.of(PANTHEON_PRIVACY_GROUP))) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessage("Transaction privateFrom must match enclave public key"); - - verify(privacyController, never()) - .createPrivateMarkerTransactionPayload( - transaction, ENCLAVE_PUBLIC_KEY1, Optional.of(PANTHEON_PRIVACY_GROUP)); } @Test @@ -173,8 +124,12 @@ public class RestrictedMultiTenancyPrivacyControllerTest { final PrivacyGroup privacyGroupWithoutPrivacyUserId = new PrivacyGroup( PRIVACY_GROUP_ID, PrivacyGroup.Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(privacyGroupWithoutPrivacyUserId); + + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); assertThatThrownBy( () -> @@ -190,20 +145,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { transaction, ENCLAVE_PUBLIC_KEY1, Optional.of(privacyGroupWithoutPrivacyUserId)); } - @Test - public void retrieveTransactionDelegatesToPrivacyController() { - final ReceiveResponse delegateRetrieveResponse = - new ReceiveResponse(new byte[] {}, PRIVACY_GROUP_ID, ENCLAVE_KEY); - when(privacyController.retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1)) - .thenReturn(delegateRetrieveResponse); - - final ReceiveResponse multiTenancyRetrieveResponse = - multiTenancyPrivacyController.retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1); - assertThat(multiTenancyRetrieveResponse) - .isEqualToComparingFieldByField(delegateRetrieveResponse); - verify(privacyController).retrieveTransaction(ENCLAVE_KEY, ENCLAVE_PUBLIC_KEY1); - } - @Test public void createPrivacyGroupDelegatesToPrivacyController() { final List addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2); @@ -225,14 +166,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { @Test public void deletesPrivacyGroupWhenPrivacyUserIdInPrivacyGroup() { - final PrivacyGroup privacyGroupWithPrivacyUserId = - new PrivacyGroup( - PRIVACY_GROUP_ID, - PrivacyGroup.Type.PANTHEON, - "", - "", - List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)).thenReturn(privacyGroupWithPrivacyUserId); when(privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) .thenReturn(ENCLAVE_PUBLIC_KEY1); @@ -245,11 +178,11 @@ public class RestrictedMultiTenancyPrivacyControllerTest { @Test public void deletePrivacyGroupFailsWithValidationExceptionWhenPrivacyGroupDoesNotContainPrivacyUserId() { - final PrivacyGroup privacyGroupWithoutPrivacyUserId = - new PrivacyGroup( - PRIVACY_GROUP_ID, PrivacyGroup.Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(privacyGroupWithoutPrivacyUserId); + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); assertThatThrownBy( () -> @@ -269,15 +202,14 @@ public class RestrictedMultiTenancyPrivacyControllerTest { "", "", List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); - when(privacyController.findOffchainPrivacyGroupByMembers(addresses, ENCLAVE_PUBLIC_KEY1)) + when(privacyController.findPrivacyGroupByMembers(addresses, ENCLAVE_PUBLIC_KEY1)) .thenReturn(new PrivacyGroup[] {privacyGroup}); final PrivacyGroup[] privacyGroups = - multiTenancyPrivacyController.findOffchainPrivacyGroupByMembers( - addresses, ENCLAVE_PUBLIC_KEY1); + multiTenancyPrivacyController.findPrivacyGroupByMembers(addresses, ENCLAVE_PUBLIC_KEY1); assertThat(privacyGroups).hasSize(1); assertThat(privacyGroups[0]).isEqualToComparingFieldByField(privacyGroup); - verify(privacyController).findOffchainPrivacyGroupByMembers(addresses, ENCLAVE_PUBLIC_KEY1); + verify(privacyController).findPrivacyGroupByMembers(addresses, ENCLAVE_PUBLIC_KEY1); } @Test @@ -286,71 +218,34 @@ public class RestrictedMultiTenancyPrivacyControllerTest { assertThatThrownBy( () -> - multiTenancyPrivacyController.findOffchainPrivacyGroupByMembers( + multiTenancyPrivacyController.findPrivacyGroupByMembers( addresses, ENCLAVE_PUBLIC_KEY1)) .isInstanceOf(MultiTenancyValidationException.class) .hasMessage("Privacy group addresses must contain the enclave public key"); } - @Test - public void determinesEeaNonceWhenPrivateFromMatchesPrivacyUserId() { - final String[] privateFor = {ENCLAVE_PUBLIC_KEY2}; - when(privacyController.determineEeaNonce( - ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1)) - .thenReturn(10L); - - final long nonce = - multiTenancyPrivacyController.determineEeaNonce( - ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1); - assertThat(nonce).isEqualTo(10); - verify(privacyController) - .determineEeaNonce(ENCLAVE_PUBLIC_KEY1, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1); - } - - @Test - public void - determineEeaNonceFailsWithValidationExceptionWhenPrivateFromDoesNotMatchPrivacyUserId() { - final String[] privateFor = {ENCLAVE_PUBLIC_KEY2}; - assertThatThrownBy( - () -> - multiTenancyPrivacyController.determineEeaNonce( - ENCLAVE_PUBLIC_KEY2, privateFor, Address.ZERO, ENCLAVE_PUBLIC_KEY1)) - .isInstanceOf(MultiTenancyValidationException.class) - .hasMessage("Transaction privateFrom must match enclave public key"); - } - @Test public void determineBesuNonceWhenPrivacyUserIdInPrivacyGroup() { - final PrivacyGroup privacyGroupWithPrivacyUserId = - new PrivacyGroup( - PRIVACY_GROUP_ID, - PrivacyGroup.Type.PANTHEON, - "", - "", - List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)).thenReturn(privacyGroupWithPrivacyUserId); - when(privacyController.determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) + when(privacyController.determineNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) .thenReturn(10L); final long nonce = - multiTenancyPrivacyController.determineBesuNonce( + multiTenancyPrivacyController.determineNonce( Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); assertThat(nonce).isEqualTo(10); - verify(privacyController) - .determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); + verify(privacyController).determineNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); } @Test public void determineBesuNonceFailsWithValidationExceptionWhenPrivacyUserIdNotInPrivacyGroup() { - final PrivacyGroup privacyGroupWithoutPrivacyUserId = - new PrivacyGroup( - PRIVACY_GROUP_ID, PrivacyGroup.Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(privacyGroupWithoutPrivacyUserId); - + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); assertThatThrownBy( () -> - multiTenancyPrivacyController.determineBesuNonce( + multiTenancyPrivacyController.determineNonce( Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) .isInstanceOf(MultiTenancyValidationException.class) .hasMessage("Privacy group must contain the enclave public key"); @@ -358,8 +253,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { @Test public void simulatePrivateTransactionWorksForValidEnclaveKey() { - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); when(privacyController.simulatePrivateTransaction(any(), any(), any(), any(long.class))) .thenReturn( Optional.of( @@ -372,15 +265,18 @@ public class RestrictedMultiTenancyPrivacyControllerTest { new CallParameter(Address.ZERO, Address.ZERO, 0, Wei.ZERO, Wei.ZERO, Bytes.EMPTY), 1); - assertThat(result.isPresent()).isTrue(); + assertThat(result).isPresent(); assertThat(result.get().getValidationResult().isValid()).isTrue(); } @Test public void simulatePrivateTransactionFailsForInvalidEnclaveKey() { - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); - + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId( + PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2, Optional.of(1L)); assertThatThrownBy( () -> multiTenancyPrivacyController.simulatePrivateTransaction( @@ -396,8 +292,6 @@ public class RestrictedMultiTenancyPrivacyControllerTest { public void getContractCodeWorksForValidEnclaveKey() { final Bytes contractCode = Bytes.fromBase64String("ZXhhbXBsZQ=="); - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); when(privacyController.getContractCode(any(), any(), any(), any())) .thenReturn(Optional.of(contractCode)); @@ -410,8 +304,11 @@ public class RestrictedMultiTenancyPrivacyControllerTest { @Test public void getContractCodeFailsForInvalidEnclaveKey() { - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); + doThrow( + new MultiTenancyValidationException( + "Privacy group must contain the enclave public key")) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2); assertThatThrownBy( () -> @@ -422,20 +319,15 @@ public class RestrictedMultiTenancyPrivacyControllerTest { @Test public void verifyPrivacyGroupMatchesEnclaveKeySucceeds() { - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); - multiTenancyPrivacyController.verifyPrivacyGroupContainsPrivacyUserId( PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); - - verify(enclave).retrievePrivacyGroup(eq(PRIVACY_GROUP_ID)); } @Test(expected = MultiTenancyValidationException.class) public void verifyPrivacyGroupDoesNotMatchEnclaveKeyThrowsException() { - when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) - .thenReturn(PANTHEON_GROUP_WITH_ENCLAVE_KEY_1); - + doThrow(MultiTenancyValidationException.class) + .when(privacyController) + .verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2); multiTenancyPrivacyController.verifyPrivacyGroupContainsPrivacyUserId( PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY2); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyControllerTest.java new file mode 100644 index 000000000..50fd77349 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyControllerTest.java @@ -0,0 +1,408 @@ +/* + * 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; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.PRIVATE_NONCE_TOO_LOW; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.EnclaveServerException; +import org.hyperledger.besu.enclave.types.PrivacyGroup; +import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.plugin.data.Restriction; +import org.hyperledger.enclave.testutil.EnclaveKeyUtils; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.io.Base64; +import org.assertj.core.api.Assertions; +import org.assertj.core.util.Lists; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class OnchainPrivacyControllerTest { + + private static final String ADDRESS1 = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String ADDRESS2 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; + private static final String PRIVACY_GROUP_ID = "DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="; + private static final String KEY = "C2bVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String HEX_STRING_32BYTES_VALUE1 = + "0x0000000000000000000000000000000000000000000000000000000000000001"; + + private OnchainPrivacyController privacyController; + private PrivateTransactionValidator privateTransactionValidator; + private PrivateNonceProvider privateNonceProvider; + private PrivateStateRootResolver privateStateRootResolver; + private PrivateTransactionSimulator privateTransactionSimulator; + private PrivateWorldStateReader privateWorldStateReader; + private Blockchain blockchain; + private PrivateStateStorage privateStateStorage; + private Enclave enclave; + private String enclavePublicKey; + private OnchainPrivacyController brokenPrivacyController; + private static final byte[] PAYLOAD = new byte[0]; + private static final String TRANSACTION_KEY = "93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="; + + private static final List PRIVACY_GROUP_ADDRESSES = List.of(ADDRESS1, ADDRESS2); + private static final Bytes SIMULATOR_RESULT_PREFIX = + Bytes.fromHexString( + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002"); + private static final Bytes SIMULATOR_RESULT = + Bytes.concatenate(SIMULATOR_RESULT_PREFIX, Base64.decode(ADDRESS1), Base64.decode(ADDRESS2)); + private static final PrivacyGroup EXPECTED_PRIVACY_GROUP = + new PrivacyGroup( + PRIVACY_GROUP_ID, PrivacyGroup.Type.ONCHAIN, "", "", PRIVACY_GROUP_ADDRESSES); + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final KeyPair KEY_PAIR = + SIGNATURE_ALGORITHM + .get() + .createKeyPair( + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); + + @Before + public void setUp() throws Exception { + blockchain = mock(Blockchain.class); + privateTransactionSimulator = mock(PrivateTransactionSimulator.class); + privateStateStorage = mock(PrivateStateStorage.class); + privateNonceProvider = mock(ChainHeadPrivateNonceProvider.class); + privateStateRootResolver = mock(PrivateStateRootResolver.class); + + privateWorldStateReader = mock(PrivateWorldStateReader.class); + + privateTransactionValidator = mockPrivateTransactionValidator(); + enclave = mockEnclave(); + + enclavePublicKey = EnclaveKeyUtils.loadKey("enclave_key_0.pub"); + + privacyController = + new OnchainPrivacyController( + blockchain, + privateStateStorage, + enclave, + privateTransactionValidator, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privateStateRootResolver); + brokenPrivacyController = + new OnchainPrivacyController( + blockchain, + privateStateStorage, + brokenMockEnclave(), + privateTransactionValidator, + privateTransactionSimulator, + privateNonceProvider, + privateWorldStateReader, + privateStateRootResolver); + } + + @Test + public void createsPayload() { + final PrivateTransaction privateTransaction = buildPrivateTransaction(1).signAndBuild(KEY_PAIR); + final SendResponse key = new SendResponse(KEY); + when(enclave.send(any(), any(), anyList())).thenReturn(key); + final String payload = + privacyController.createPrivateMarkerTransactionPayload( + privateTransaction, ADDRESS1, Optional.of(EXPECTED_PRIVACY_GROUP)); + assertThat(payload).isNotNull().isEqualTo(KEY); + } + + @Test + public void createsPayloadForAdding() { + mockingForCreatesPayloadForAdding(); + final PrivateTransaction privateTransaction = + buildPrivateTransaction(1) + .payload(OnchainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE) + .to(PrivacyParameters.ONCHAIN_PRIVACY_PROXY) + .signAndBuild(KEY_PAIR); + final String payload = + privacyController.createPrivateMarkerTransactionPayload( + privateTransaction, ADDRESS1, Optional.of(EXPECTED_PRIVACY_GROUP)); + assertThat(payload) + .isNotNull() + .isEqualTo( + Bytes.concatenate(Bytes.fromBase64String(KEY), Bytes.fromBase64String(KEY)) + .toBase64String()); + } + + @Test + public void verifiesGroupContainsUserId() { + final OnchainPrivacyGroupContract onchainPrivacyGroupContract = + mock(OnchainPrivacyGroupContract.class); + when(onchainPrivacyGroupContract.getPrivacyGroupByIdAndBlockNumber(any(), any())) + .thenReturn(Optional.of(EXPECTED_PRIVACY_GROUP)); + privacyController.setOnchainPrivacyGroupContract(onchainPrivacyGroupContract); + privacyController.verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ADDRESS1); + } + + @Test + public void findOnchainPrivacyGroups() { + mockingForFindPrivacyGroupByMembers(); + mockingForFindPrivacyGroupById(); + + final List privacyGroups = + List.of(privacyController.findPrivacyGroupByMembers(PRIVACY_GROUP_ADDRESSES, ADDRESS1)); + assertThat(privacyGroups).hasSize(1); + assertThat(privacyGroups.get(0)).isEqualToComparingFieldByField(EXPECTED_PRIVACY_GROUP); + verify(privateStateStorage).getPrivacyGroupHeadBlockMap(any()); + verify(privateTransactionSimulator).process(any(), any()); + } + + @Test + public void createsPrivacyGroup() { + Assertions.assertThatThrownBy( + () -> privacyController.createPrivacyGroup(Lists.emptyList(), "", "", ADDRESS1)) + .isInstanceOf(PrivacyConfigurationNotSupportedException.class) + .hasMessageContaining("Method not supported when using onchain privacy"); + } + + @Test + public void deletesPrivacyGroup() { + Assertions.assertThatThrownBy( + () -> privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ADDRESS1)) + .isInstanceOf(PrivacyConfigurationNotSupportedException.class) + .hasMessageContaining("Method not supported when using onchain privacy"); + } + + @Test + public void sendTransactionWhenEnclaveFailsThrowsEnclaveError() { + final TransactionProcessingResult transactionProcessingResult = + new TransactionProcessingResult( + TransactionProcessingResult.Status.SUCCESSFUL, + Lists.emptyList(), + 0, + 0, + Bytes32.ZERO, + ValidationResult.valid(), + Optional.empty()); + when(privateTransactionSimulator.process(any(), any())) + .thenReturn(Optional.of(transactionProcessingResult)); + Assertions.setMaxStackTraceElementsDisplayed(500); + assertThatExceptionOfType(EnclaveServerException.class) + .isThrownBy( + () -> + brokenPrivacyController.createPrivateMarkerTransactionPayload( + buildPrivateTransaction(0).signAndBuild(KEY_PAIR), + ADDRESS1, + Optional.of(EXPECTED_PRIVACY_GROUP))); + } + + @Test + public void validateTransactionWithTooLowNonceReturnsError() { + when(privateTransactionValidator.validate(any(), any(), anyBoolean())) + .thenReturn(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); + + final PrivateTransaction transaction = buildPrivateTransaction(0).build(); + final ValidationResult validationResult = + privacyController.validatePrivateTransaction(transaction, ADDRESS1); + assertThat(validationResult).isEqualTo(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); + } + + @Test + public void validateTransactionWithIncorrectNonceReturnsError() { + when(privateTransactionValidator.validate(any(), any(), anyBoolean())) + .thenReturn(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); + + final PrivateTransaction transaction = buildPrivateTransaction(2).build(); + + final ValidationResult validationResult = + privacyController.validatePrivateTransaction(transaction, ADDRESS1); + assertThat(validationResult).isEqualTo(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); + } + + @Test + public void retrievesTransaction() { + when(enclave.receive(anyString(), anyString())) + .thenReturn(new ReceiveResponse(PAYLOAD, PRIVACY_GROUP_ID, null)); + + final ReceiveResponse receiveResponse = + privacyController.retrieveTransaction(TRANSACTION_KEY, ADDRESS1); + + assertThat(receiveResponse.getPayload()).isEqualTo(PAYLOAD); + assertThat(receiveResponse.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); + verify(enclave).receive(TRANSACTION_KEY, enclavePublicKey); + } + + @Test + public void findsPrivacyGroupById() { + final PrivacyGroup privacyGroup = + new PrivacyGroup( + PRIVACY_GROUP_ID, PrivacyGroup.Type.ONCHAIN, "", "", PRIVACY_GROUP_ADDRESSES); + mockingForFindPrivacyGroupById(); + + final Optional privacyGroupFound = + privacyController.findPrivacyGroupByGroupId(PRIVACY_GROUP_ID, ADDRESS1); + assertThat(privacyGroupFound).isPresent(); + assertThat(privacyGroupFound.get()).isEqualToComparingFieldByField(privacyGroup); + } + + @Test + public void findsPrivacyGroupByIdEmpty() { + when(privateTransactionSimulator.process(any(), any())).thenReturn(Optional.empty()); + + final Optional privacyGroupFound = + privacyController.findPrivacyGroupByGroupId(PRIVACY_GROUP_ID, ADDRESS1); + assertThat(privacyGroupFound).isEmpty(); + } + + private Enclave brokenMockEnclave() { + final Enclave mockEnclave = mock(Enclave.class); + when(mockEnclave.send(anyString(), anyString(), anyList())) + .thenThrow(EnclaveServerException.class); + return mockEnclave; + } + + private static PrivateTransaction.Builder buildPrivateTransaction(final long nonce) { + return PrivateTransaction.builder() + .nonce(nonce) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57")) + .value(Wei.ZERO) + .payload(Bytes.fromHexString("0x00")) + .sender(Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73")) + .chainId(BigInteger.valueOf(1337)) + .restriction(Restriction.RESTRICTED) + .privateFrom(Base64.decode(ADDRESS1)) + .privacyGroupId(Base64.decode(PRIVACY_GROUP_ID)); + } + + private Enclave mockEnclave() { + final Enclave mockEnclave = mock(Enclave.class); + return mockEnclave; + } + + private PrivateTransactionValidator mockPrivateTransactionValidator() { + final PrivateTransactionValidator validator = mock(PrivateTransactionValidator.class); + return validator; + } + + private void mockingForFindPrivacyGroupByMembers() { + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + new PrivacyGroupHeadBlockMap( + Map.of(Bytes32.wrap(Bytes.fromBase64String(PRIVACY_GROUP_ID)), Hash.ZERO)); + when(privateStateStorage.getPrivacyGroupHeadBlockMap(any())) + .thenReturn(Optional.of(privacyGroupHeadBlockMap)); + } + + private void mockingForFindPrivacyGroupById() { + when(privateTransactionSimulator.process(any(), any())) + .thenReturn( + Optional.of( + new TransactionProcessingResult( + TransactionProcessingResult.Status.SUCCESSFUL, + emptyList(), + 0, + 0, + SIMULATOR_RESULT, + ValidationResult.valid(), + Optional.empty()))); + } + + private void mockingForCreatesPayloadForAdding() { + final SendResponse key = new SendResponse(KEY); + when(enclave.send(any(), any(), anyList())).thenReturn(key); + final Map bytes32HashMap = new HashMap<>(); + final Bytes32 pgBytes = Bytes32.wrap(Base64.decode(PRIVACY_GROUP_ID)); + bytes32HashMap.put(pgBytes, Hash.ZERO); + when(blockchain.getChainHeadHash()).thenReturn(Hash.ZERO); + final Optional privacyGroupHeadBlockMap = + Optional.of(new PrivacyGroupHeadBlockMap(bytes32HashMap)); + when(privateStateStorage.getPrivacyGroupHeadBlockMap(Hash.ZERO)) + .thenReturn(privacyGroupHeadBlockMap); + final List privateTransactionMetadata = + List.of(new PrivateTransactionMetadata(Hash.ZERO, Hash.ZERO)); + final PrivateBlockMetadata privateBlockMetadata = + new PrivateBlockMetadata(privateTransactionMetadata); + when(privateStateStorage.getPrivateBlockMetadata(any(), eq(pgBytes))) + .thenReturn(Optional.of(privateBlockMetadata)); + final BlockHeader blockHeaderMock = mock(BlockHeader.class); + final Optional maybeBlockHeader = Optional.of(blockHeaderMock); + when(blockchain.getBlockHeader(any())).thenReturn(maybeBlockHeader); + final Hash hash0x01 = Hash.fromHexString(HEX_STRING_32BYTES_VALUE1); + when(blockHeaderMock.getParentHash()).thenReturn(hash0x01); + when(privateStateStorage.getPrivacyGroupHeadBlockMap(hash0x01)).thenReturn(Optional.empty()); + final Transaction transaction = + Transaction.builder() + .gasLimit(0) + .gasPrice(Wei.ZERO) + .nonce(0) + .payload(Bytes.fromHexString(HEX_STRING_32BYTES_VALUE1)) + .value(Wei.ZERO) + .to(null) + .guessType() + .signAndBuild(KEY_PAIR); + when(blockchain.getTransactionByHash(any())).thenReturn(Optional.ofNullable(transaction)); + final PrivateTransactionWithMetadata privateTransactionWithMetadata = + new PrivateTransactionWithMetadata( + buildPrivateTransaction(3).signAndBuild(KEY_PAIR), + new PrivateTransactionMetadata(Hash.ZERO, Hash.ZERO)); + final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); + privateTransactionWithMetadata.writeTo(bytesValueRLPOutput); + final byte[] txPayload = + bytesValueRLPOutput.encoded().toBase64String().getBytes(StandardCharsets.UTF_8); + when(enclave.receive(any(), any())) + .thenReturn(new ReceiveResponse(txPayload, PRIVACY_GROUP_ID, ADDRESS2)); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyControllerTest.java index c8b2df6c4..db7d49db0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyControllerTest.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.privacy; import static com.google.common.collect.Lists.newArrayList; -import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE; @@ -24,7 +23,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -39,10 +37,8 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.EnclaveServerException; import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; @@ -54,13 +50,11 @@ import org.hyperledger.enclave.testutil.EnclaveKeyUtils; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.io.Base64; import org.junit.Before; import org.junit.Test; @@ -70,7 +64,6 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class RestrictedDefaultPrivacyControllerTest { - private static final String TRANSACTION_KEY = "93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="; private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); private static final KeyPair KEY_PAIR = @@ -82,7 +75,6 @@ public class RestrictedDefaultPrivacyControllerTest { .createPrivateKey( new BigInteger( "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); - private static final byte[] PAYLOAD = new byte[0]; private static final List PRIVACY_GROUP_ADDRESSES = newArrayList("8f2a", "fb23"); private static final String PRIVACY_GROUP_NAME = "pg_name"; private static final String PRIVACY_GROUP_DESCRIPTION = "pg_desc"; @@ -90,10 +82,8 @@ public class RestrictedDefaultPrivacyControllerTest { private static final String ENCLAVE_KEY2 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; private static final String PRIVACY_GROUP_ID = "DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="; private static final ArrayList LOGS = new ArrayList<>(); - private static final String MOCK_TRANSACTION_SIMULATOR_RESULT_OUTPUT_BYTES_PREFIX = - "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002"; - private PrivacyController privacyController; + private RestrictedDefaultPrivacyController privacyController; private PrivacyController brokenPrivacyController; private PrivateTransactionValidator privateTransactionValidator; private Enclave enclave; @@ -107,9 +97,6 @@ public class RestrictedDefaultPrivacyControllerTest { private Enclave mockEnclave() { final Enclave mockEnclave = mock(Enclave.class); - final ReceiveResponse receiveResponse = - new ReceiveResponse(new byte[0], PRIVACY_GROUP_ID, null); - when(mockEnclave.receive(any(), any())).thenReturn(receiveResponse); return mockEnclave; } @@ -163,44 +150,6 @@ public class RestrictedDefaultPrivacyControllerTest { privateStateRootResolver); } - @Test - public void findOnchainPrivacyGroups() { - final List privacyGroupAddresses = newArrayList(ENCLAVE_PUBLIC_KEY, ENCLAVE_KEY2); - - final PrivacyGroup privacyGroup = - new PrivacyGroup( - PRIVACY_GROUP_ID, PrivacyGroup.Type.ONCHAIN, "", "", privacyGroupAddresses); - - final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = - new PrivacyGroupHeadBlockMap( - Map.of(Bytes32.wrap(Bytes.fromBase64String(PRIVACY_GROUP_ID)), Hash.ZERO)); - when(privateStateStorage.getPrivacyGroupHeadBlockMap(any())) - .thenReturn(Optional.of(privacyGroupHeadBlockMap)); - - when(privateTransactionSimulator.process(any(), any())) - .thenReturn( - Optional.of( - new TransactionProcessingResult( - TransactionProcessingResult.Status.SUCCESSFUL, - emptyList(), - 0, - 0, - Bytes.fromHexString( - MOCK_TRANSACTION_SIMULATOR_RESULT_OUTPUT_BYTES_PREFIX - + Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY).toUnprefixedHexString() - + Bytes.fromBase64String(ENCLAVE_KEY2).toUnprefixedHexString()), - ValidationResult.valid(), - Optional.empty()))); - - final List privacyGroups = - privacyController.findOnchainPrivacyGroupByMembers( - privacyGroupAddresses, ENCLAVE_PUBLIC_KEY); - assertThat(privacyGroups).hasSize(1); - assertThat(privacyGroups.get(0)).isEqualToComparingFieldByField(privacyGroup); - verify(privateStateStorage).getPrivacyGroupHeadBlockMap(any()); - verify(privateTransactionSimulator).process(any(), any()); - } - @Test public void sendTransactionWhenEnclaveFailsThrowsEnclaveError() { assertThatExceptionOfType(EnclaveServerException.class) @@ -233,19 +182,6 @@ public class RestrictedDefaultPrivacyControllerTest { assertThat(validationResult).isEqualTo(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); } - @Test - public void retrievesTransaction() { - when(enclave.receive(anyString(), anyString())) - .thenReturn(new ReceiveResponse(PAYLOAD, PRIVACY_GROUP_ID, null)); - - final ReceiveResponse receiveResponse = - privacyController.retrieveTransaction(TRANSACTION_KEY, ENCLAVE_PUBLIC_KEY); - - assertThat(receiveResponse.getPayload()).isEqualTo(PAYLOAD); - assertThat(receiveResponse.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); - verify(enclave).receive(TRANSACTION_KEY, enclavePublicKey); - } - @Test public void createsPrivacyGroup() { final PrivacyGroup enclavePrivacyGroupResponse = @@ -297,78 +233,12 @@ public class RestrictedDefaultPrivacyControllerTest { when(enclave.findPrivacyGroup(any())).thenReturn(new PrivacyGroup[] {privacyGroup}); final PrivacyGroup[] privacyGroups = - privacyController.findOffchainPrivacyGroupByMembers( - PRIVACY_GROUP_ADDRESSES, ENCLAVE_PUBLIC_KEY); + privacyController.findPrivacyGroupByMembers(PRIVACY_GROUP_ADDRESSES, ENCLAVE_PUBLIC_KEY); assertThat(privacyGroups).hasSize(1); assertThat(privacyGroups[0]).isEqualToComparingFieldByField(privacyGroup); verify(enclave).findPrivacyGroup(PRIVACY_GROUP_ADDRESSES); } - @Test - public void determinesNonceForEeaRequest() { - final Address address = Address.fromHexString("55"); - final long reportedNonce = 8L; - final PrivacyGroup[] returnedGroups = - new PrivacyGroup[] { - new PrivacyGroup( - PRIVACY_GROUP_ID, - PrivacyGroup.Type.LEGACY, - "Group1_Name", - "Group1_Desc", - emptyList()), - }; - - when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups); - when(privateNonceProvider.getNonce(any(Address.class), any(Bytes32.class))).thenReturn(8L); - - final long nonce = - privacyController.determineEeaNonce( - ENCLAVE_PUBLIC_KEY, new String[] {ENCLAVE_KEY2}, address, ENCLAVE_PUBLIC_KEY); - - assertThat(nonce).isEqualTo(reportedNonce); - verify(enclave) - .findPrivacyGroup( - argThat((m) -> m.containsAll(newArrayList(ENCLAVE_PUBLIC_KEY, ENCLAVE_KEY2)))); - } - - @Test - public void determineNonceForEeaRequestWithNoMatchingGroupReturnsZero() { - final long reportedNonce = 0L; - final Address address = Address.fromHexString("55"); - final PrivacyGroup[] returnedGroups = new PrivacyGroup[0]; - - when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups); - - final long nonce = - privacyController.determineEeaNonce( - "privateFrom", new String[] {"first", "second"}, address, ENCLAVE_PUBLIC_KEY); - - assertThat(nonce).isEqualTo(reportedNonce); - verify(enclave) - .findPrivacyGroup( - argThat((m) -> m.containsAll(newArrayList("first", "second", "privateFrom")))); - } - - @Test - public void determineNonceForEeaRequestWithMoreThanOneMatchingGroupThrowsException() { - final Address address = Address.fromHexString("55"); - final PrivacyGroup[] returnedGroups = - new PrivacyGroup[] { - new PrivacyGroup( - "Group1", PrivacyGroup.Type.LEGACY, "Group1_Name", "Group1_Desc", emptyList()), - new PrivacyGroup( - "Group2", PrivacyGroup.Type.LEGACY, "Group2_Name", "Group2_Desc", emptyList()), - }; - - when(enclave.findPrivacyGroup(any())).thenReturn(returnedGroups); - - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy( - () -> - privacyController.determineEeaNonce( - "privateFrom", new String[] {"first", "second"}, address, ENCLAVE_PUBLIC_KEY)); - } - @Test public void simulatingPrivateTransactionWorks() { final CallParameter callParameter = mock(CallParameter.class);