Refactor privacy controller (#2971)

* refactor of PrivacyController

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>
pull/3039/head
Stefan Pingel 3 years ago committed by GitHub
parent 2f1871852e
commit 4d72369469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java
  2. 8
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java
  3. 18
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java
  4. 3
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java
  5. 106
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java
  6. 4
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java
  7. 9
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/OnchainMultiTenancyAcceptanceTest.java
  8. 2
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  9. 4
      enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java
  10. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java
  11. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java
  12. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java
  13. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransaction.java
  14. 60
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransaction.java
  15. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java
  16. 13
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java
  17. 34
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java
  18. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java
  19. 32
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java
  20. 37
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java
  21. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java
  22. 6
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java
  23. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroup.java
  24. 26
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java
  25. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/AbstractPrivateSubscriptionMethod.java
  26. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribe.java
  27. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribe.java
  28. 25
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivateWebSocketMethodsFactory.java
  29. 4
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOffchainEeaSendRawTransactionTest.java
  30. 6
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/RestrictedOnchainEeaSendRawTransactionTest.java
  31. 16
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java
  32. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java
  33. 12
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java
  34. 22
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCountTest.java
  35. 38
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java
  36. 30
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java
  37. 34
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java
  38. 9
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCountTest.java
  39. 21
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java
  40. 31
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java
  41. 15
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroupTest.java
  42. 4
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java
  43. 30
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivSubscribeTest.java
  44. 34
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/methods/PrivUnsubscribeTest.java
  45. 122
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java
  46. 56
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java
  47. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/ChainHeadPrivateNonceProvider.java
  48. 165
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java
  49. 364
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyController.java
  50. 50
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/OnchainUtil.java
  51. 167
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java
  52. 33
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java
  53. 31
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyGroupUtil.java
  54. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java
  55. 420
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyController.java
  56. 326
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/RestrictedMultiTenancyPrivacyController.java
  57. 31
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerOnchainTest.java
  58. 194
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java
  59. 408
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/OnchainPrivacyControllerTest.java
  60. 134
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/RestrictedDefaultPrivacyControllerTest.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));
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.
// 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++) {

@ -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);

@ -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

@ -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(

@ -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<Bytes> 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<PrivacyGroup> 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<PrivacyGroup> 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<String> 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<String> 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);
}
}

@ -172,7 +172,9 @@ public class MultiTenancyValidationFailAcceptanceTest extends AcceptanceTestBase
final Transaction<Integer> 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

@ -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(
final String actual =
privacyNode
.execute(
privacyTransactions.privGetCode(
privacyGroupId,
Address.fromHexString(eventEmitter.getContractAddress()),
"latest"))
.toHexString())
.isEqualTo(eventEmitterDeployed);
.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(

@ -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 =

@ -65,6 +65,10 @@ public class PrivacyGroup implements Serializable {
this.members = members;
}
public void addMembers(final List<String> participantsFromParameter) {
members.addAll(participantsFromParameter);
}
public PrivacyGroup() {}
public PrivacyGroup(

@ -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);
if (privacyController instanceof MultiTenancyPrivacyController) {
checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(requestContext, privacyGroupId);
}
final List<LogWithMetadata> logs = filterManager.logsChanges(filterId);
if (logs != null) {
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), new LogsResult(logs));

@ -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);
if (privacyController instanceof MultiTenancyPrivacyController) {
checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(request, privacyGroupId);
}
final List<LogWithMetadata> logs = filterManager.logs(filterId);
if (logs != null) {

@ -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);
if (privacyController instanceof MultiTenancyPrivacyController) {
checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(request, privacyGroupId);
}
return new JsonRpcSuccessResponse(
request.getRequest().getId(), filterManager.uninstallFilter(filterId));

@ -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<PrivacyGroup> maybePrivacyGroup =
findOffchainPrivacyGroup(
privacyController, privateTransaction.getPrivacyGroupId(), privacyUserId);
privateTransaction
.getPrivacyGroupId()
.flatMap(
privacyGroupId ->
privacyController.findPrivacyGroupByGroupId(
privacyGroupId.toBase64String(), privacyUserId));
final String privateTransactionLookupId =
privacyController.createPrivateMarkerTransactionPayload(

@ -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> user) {
if (privateTransaction.getPrivacyGroupId().isEmpty()) {
final Optional<Bytes> 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> privacyGroup =
findOnchainPrivacyGroup(
privacyController,
privateTransaction.getPrivacyGroupId(),
privacyUserId,
privateTransaction);
Optional<PrivacyGroup> 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<String> 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<String> 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<String> maybePrivateTransactionLookupId) {
return maybePrivateTransactionLookupId.isPresent()
? Bytes.concatenate(
Bytes.fromBase64String(privateTransactionLookupId),
Bytes.fromBase64String(maybePrivateTransactionLookupId.get()))
.toBase64String()
: privateTransactionLookupId;
}
}

@ -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(

@ -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);

@ -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,15 +88,31 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_ID_NOT_AVAILABLE);
}
final Optional<PrivacyGroup> maybePrivacyGroup =
onchainPrivacyGroupsEnabled
? findOnchainPrivacyGroup(
privacyController, maybePrivacyGroupId, privacyUserId, privateTransaction)
: findOffchainPrivacyGroup(privacyController, maybePrivacyGroupId, privacyUserId);
if (onchainPrivacyGroupsEnabled && maybePrivacyGroup.isEmpty()) {
Optional<PrivacyGroup> maybePrivacyGroup =
maybePrivacyGroupId.flatMap(
gId ->
privacyController.findPrivacyGroupByGroupId(gId.toBase64String(), privacyUserId));
if (onchainPrivacyGroupsEnabled) {
if (OnchainUtil.isGroupAdditionTransaction(privateTransaction)) {
final List<String> 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<TransactionInvalidReason> validationResult =
privacyController.validatePrivateTransaction(privateTransaction, privacyUserId);

@ -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) {

@ -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<Bytes> toAddresses =
Arrays.stream(privateFor).map(Bytes::fromBase64String).collect(Collectors.toList());
final Bytes32 privacyGroupId = PrivacyGroupUtil.calculateEeaPrivacyGroupId(from, toAddresses);
return privacyController.determineNonce(
address, privacyGroupId.toBase64String(), privacyUserId);
}
}

@ -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<Long> toBlock) {
final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser());
privacyController.verifyPrivacyGroupContainsPrivacyUserId(
privacyGroupId, privacyUserId, toBlock);
}
}

@ -61,7 +61,7 @@ public class PrivGetTransactionCount implements JsonRpcMethod {
try {
final long nonce =
privacyController.determineBesuNonce(
privacyController.determineNonce(
address,
privacyGroupId,
privacyIdProvider.getPrivacyUserId(requestContext.getUser()));

@ -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
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);

@ -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<PrivacyGroup> 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));
}
}

@ -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,7 +136,18 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
privateNonceProvider,
privacyParameters.getPrivateWorldStateReader());
} else {
final RestrictedDefaultPrivacyController restrictedDefaultPrivacyController =
final PrivacyController privacyController;
if (privacyParameters.isOnchainPrivacyGroupsEnabled()) {
privacyController =
new OnchainPrivacyController(
getBlockchainQueries().getBlockchain(),
privacyParameters,
chainId,
createPrivateTransactionSimulator(),
privateNonceProvider,
privacyParameters.getPrivateWorldStateReader());
} else {
privacyController =
new RestrictedDefaultPrivacyController(
getBlockchainQueries().getBlockchain(),
privacyParameters,
@ -143,14 +155,10 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
createPrivateTransactionSimulator(),
privateNonceProvider,
privacyParameters.getPrivateWorldStateReader());
}
return privacyParameters.isMultiTenancyEnabled()
? new RestrictedMultiTenancyPrivacyController(
restrictedDefaultPrivacyController,
chainId,
privacyParameters.getEnclave(),
privacyParameters.isOnchainPrivacyGroupsEnabled())
: restrictedDefaultPrivacyController;
? new MultiTenancyPrivacyController(privacyController)
: privacyController;
}
}

@ -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(

@ -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);
if (privacyController instanceof MultiTenancyPrivacyController) {
checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(
requestContext, subscribeRequest.getPrivacyGroupId());
}
final Long subscriptionId = subscriptionManager().subscribe(subscribeRequest);

@ -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);
if (privacyController instanceof MultiTenancyPrivacyController) {
checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(
requestContext, unsubscribeRequest.getPrivacyGroupId());
}
final boolean unsubscribed = subscriptionManager().unsubscribe(unsubscribeRequest);

@ -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,7 +76,18 @@ public class PrivateWebSocketMethodsFactory {
createPrivateNonceProvider(),
privacyParameters.getPrivateWorldStateReader());
} else {
final RestrictedDefaultPrivacyController restrictedDefaultPrivacyController =
final PrivacyController restrictedPrivacyController;
if (privacyParameters.isOnchainPrivacyGroupsEnabled()) {
restrictedPrivacyController =
new OnchainPrivacyController(
blockchainQueries.getBlockchain(),
privacyParameters,
chainId,
createPrivateTransactionSimulator(),
createPrivateNonceProvider(),
privacyParameters.getPrivateWorldStateReader());
} else {
restrictedPrivacyController =
new RestrictedDefaultPrivacyController(
blockchainQueries.getBlockchain(),
privacyParameters,
@ -83,13 +95,10 @@ public class PrivateWebSocketMethodsFactory {
createPrivateTransactionSimulator(),
createPrivateNonceProvider(),
privacyParameters.getPrivateWorldStateReader());
}
return privacyParameters.isMultiTenancyEnabled()
? new RestrictedMultiTenancyPrivacyController(
restrictedDefaultPrivacyController,
chainId,
privacyParameters.getEnclave(),
privacyParameters.isOnchainPrivacyGroupsEnabled())
: restrictedDefaultPrivacyController;
? new MultiTenancyPrivacyController(restrictedPrivacyController)
: restrictedPrivacyController;
}
}

@ -80,12 +80,12 @@ public class RestrictedOffchainEeaSendRawTransactionTest extends BaseEeaSendRawT
when(privacyController.createPrivateMarkerTransactionPayload(any(), any(), any()))
.thenReturn(MOCK_ORION_KEY);
Optional<PrivacyGroup> pantheonPrivacyGroup =
final Optional<PrivacyGroup> 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());

@ -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(

@ -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"),

@ -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);

@ -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<PrivacyGroup> result = (List<PrivacyGroup>) 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);
}
}

@ -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);

@ -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(

@ -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,

@ -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<LogWithMetadata> logWithMetadataList(final int length) {
return IntStream.range(0, length).mapToObj(this::logWithMetadata).collect(Collectors.toList());
}

@ -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};

@ -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(

@ -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);
}
}

@ -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<PrivacyGroup> result = (List<PrivacyGroup>) 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);
}
}

@ -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) {

@ -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);

@ -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);
}
}

@ -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<BigInteger> 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<TransactionInvalidReason> 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<TransactionProcessingResult> simulatePrivateTransaction(
final String privacyGroupId,
final String privacyUserId,
final CallParameter callParams,
final long blockNumber) {
return privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber);
}
@Override
public Optional<Bytes> getContractCode(
final String privacyGroupId,
final Address contractAddress,
final Hash blockHash,
final String privacyUserId) {
return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress);
}
@Override
public Optional<Hash> 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()));
}
}

@ -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<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash(
final Hash pmtHash, final String enclaveKey) {
return privateTransactionLocator.findByPmtHash(pmtHash, enclaveKey);
}
}

@ -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

@ -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<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash(
final Hash pmtHash, final String enclaveKey) {
return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey);
}
@Override
public String createPrivateMarkerTransactionPayload(
final PrivateTransaction privateTransaction,
final String privacyUserId,
final Optional<PrivacyGroup> maybePrivacyGroup) {
final Optional<Bytes> maybePrivacyGroupId = privateTransaction.getPrivacyGroupId();
if (maybePrivacyGroupId.isPresent()) {
verifyPrivacyGroupContainsPrivacyUserId(
maybePrivacyGroupId.get().toBase64String(), privacyUserId);
}
return privacyController.createPrivateMarkerTransactionPayload(
privateTransaction, privacyUserId, maybePrivacyGroup);
}
@Override
public PrivacyGroup createPrivacyGroup(
final List<String> 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<String> 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<TransactionInvalidReason> 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<TransactionProcessingResult> 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<PrivacyGroup> findPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId) {
final Optional<PrivacyGroup> maybePrivacyGroup =
privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId);
checkGroupParticipation(maybePrivacyGroup, privacyUserId);
return maybePrivacyGroup;
}
private void checkGroupParticipation(
final Optional<PrivacyGroup> 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<Bytes> 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<Long> blockNumber)
throws MultiTenancyValidationException {
privacyController.verifyPrivacyGroupContainsPrivacyUserId(
privacyGroupId, privacyUserId, blockNumber);
}
@Override
public Optional<Hash> getStateRootByBlockNumber(
final String privacyGroupId, final String privacyUserId, final long blockNumber) {
verifyPrivacyGroupContainsPrivacyUserId(
privacyGroupId, privacyUserId, Optional.of(blockNumber));
return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber);
}
}

@ -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<BigInteger> 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> 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<String> optionalSecondPart =
buildAndSendAddPayload(
privateTransaction,
Bytes32.wrap(privateTransaction.getPrivacyGroupId().orElseThrow()),
privacyUserId);
return buildCompoundLookupId(firstPart, optionalSecondPart);
}
@Override
public Optional<PrivacyGroup> findPrivacyGroupByGroupId(
final String privacyGroupId, final String enclaveKey) {
// get the privateFor list from the management contract
final Optional<TransactionProcessingResult> 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<String> addresses, final String privacyUserId) {
final ArrayList<PrivacyGroup> privacyGroups = new ArrayList<>();
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.empty());
privacyGroupHeadBlockMap
.keySet()
.forEach(
c -> {
final Optional<PrivacyGroup> 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<String> 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<Long> blockNumber) {
final Optional<PrivacyGroup> 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<String> decodeParticipantList(final Bytes rlpEncodedList) {
final ArrayList<String> 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<PrivateTransactionMetadata> buildTransactionMetadataList(
final Bytes privacyGroupId) {
final List<PrivateTransactionMetadata> 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<PrivateTransactionWithMetadata> retrievePrivateTransactions(
final Bytes32 privacyGroupId,
final List<PrivateTransactionMetadata> privateTransactionMetadataList,
final String privacyUserId) {
final ArrayList<PrivateTransactionWithMetadata> 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<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) {
final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey);
return PrivateTransactionWithMetadata.readListFromPayload(
Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload())));
}
private Optional<String> buildAndSendAddPayload(
final PrivateTransaction privateTransaction,
final Bytes32 privacyGroupId,
final String privacyUserId) {
if (OnchainUtil.isGroupAdditionTransaction(privateTransaction)) {
final List<PrivateTransactionMetadata> privateTransactionMetadataList =
buildTransactionMetadataList(privacyGroupId);
if (!privateTransactionMetadataList.isEmpty()) {
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList =
retrievePrivateTransactions(
privacyGroupId, privateTransactionMetadataList, privacyUserId);
final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList);
final List<String> 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<String> maybePrivateTransactionLookupId) {
return maybePrivateTransactionLookupId.isPresent()
? Bytes.concatenate(
Bytes.fromBase64String(privateTransactionLookupId),
Bytes.fromBase64String(maybePrivateTransactionLookupId.get()))
.toBase64String()
: privateTransactionLookupId;
}
private Bytes serializeAddToGroupPayload(
final List<PrivateTransactionWithMetadata> 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<PrivacyGroup> maybePrivacyGroup) {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
final PrivacyGroup privacyGroup = maybePrivacyGroup.orElseThrow();
final Optional<TransactionProcessingResult> version =
privateTransactionSimulator.process(
privateTransaction.getPrivacyGroupId().orElseThrow().toBase64String(),
buildCallParams(GET_VERSION_METHOD_SIGNATURE));
new VersionedPrivateTransaction(privateTransaction, version).writeTo(rlpOutput);
final List<String> 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;
}
}

@ -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<Address> to = privateTransaction.getTo();
return to.isPresent()
&& to.get().equals(ONCHAIN_PRIVACY_PROXY)
&& privateTransaction
.getPayload()
.toHexString()
.startsWith(ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString());
}
public static List<String> getParticipantsFromParameter(final Bytes input) {
final List<String> 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;
}
}

@ -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<TransactionInvalidReason> 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<ExecutedPrivateTransaction> 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<String> addresses,
@ -152,101 +121,6 @@ public class PluginPrivacyController implements PrivacyController {
"Method not supported when using PrivacyPlugin");
}
@Override
public PrivacyGroup[] findOffchainPrivacyGroupByMembers(
final List<String> 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<TransactionProcessingResult> 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<Bytes> 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<Hash> 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<String> 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<PrivacyGroup> findOffchainPrivacyGroupByGroupId(
final String toBase64String, final String privacyUserId) {
return findPrivacyGroupByGroupId(toBase64String, privacyUserId);
}
@Override
public Optional<PrivacyGroup> findPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId) {
@ -262,33 +136,12 @@ public class PluginPrivacyController implements PrivacyController {
}
@Override
public List<PrivacyGroup> findOnchainPrivacyGroupByMembers(
public PrivacyGroup[] findPrivacyGroupByMembers(
final List<String> asList, final String privacyUserId) {
throw new PrivacyConfigurationNotSupportedException(
"Method not supported when using PrivacyPlugin");
}
@Override
public Optional<PrivacyGroup> findOnchainPrivacyGroupAndAddNewMembers(
final Bytes privacyGroupId,
final String privacyUserId,
final PrivateTransaction privateTransaction) {
throw new PrivacyConfigurationNotSupportedException(
"Method not supported when using PrivacyPlugin");
}
@Override
public List<PrivateTransactionWithMetadata> 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<Long> 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");
}
}

@ -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> privacyGroup);
ReceiveResponse retrieveTransaction(String enclaveKey, String privacyUserId);
PrivacyGroup createPrivacyGroup(
List<String> addresses, String name, String description, String privacyUserId);
String deletePrivacyGroup(String privacyGroupId, String privacyUserId);
PrivacyGroup[] findOffchainPrivacyGroupByMembers(List<String> addresses, String privacyUserId);
PrivacyGroup[] findPrivacyGroupByMembers(List<String> addresses, String privacyUserId);
Optional<PrivacyGroup> findPrivacyGroupByGroupId(String privacyGroupId, String privacyUserId);
ValidationResult<TransactionInvalidReason> 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<TransactionProcessingResult> simulatePrivateTransaction(
final String privacyGroupId,
@ -62,30 +57,12 @@ public interface PrivacyController {
final CallParameter callParams,
final long blockNumber);
Optional<String> buildAndSendAddPayload(
PrivateTransaction privateTransaction, Bytes32 privacyGroupId, String privacyUserId);
Optional<PrivacyGroup> findOffchainPrivacyGroupByGroupId(
String privacyGroupId, String privacyUserId);
Optional<PrivacyGroup> findPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId);
List<PrivacyGroup> findOnchainPrivacyGroupByMembers(List<String> asList, String privacyUserId);
Optional<Bytes> getContractCode(
final String privacyGroupId,
final Address contractAddress,
final Hash blockHash,
final String privacyUserId);
Optional<PrivacyGroup> findOnchainPrivacyGroupAndAddNewMembers(
Bytes privacyGroupId, String privacyUserId, final PrivateTransaction privateTransaction);
List<PrivateTransactionWithMetadata> 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<Long> blockNumber)
throws MultiTenancyValidationException;
PrivateTransactionSimulator getTransactionSimulator();
Optional<Hash> getStateRootByBlockNumber(
final String privacyGroupId, final String privacyUserId, final long blockNumber);
}

@ -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<Bytes> privateFor) {
final List<Bytes> privacyGroupIds = new ArrayList<>();
privacyGroupIds.add(privateFrom);
privacyGroupIds.addAll(privateFor);
final List<Bytes> privacyGroupMembers = new ArrayList<>();
privacyGroupMembers.add(privateFrom);
privacyGroupMembers.addAll(privateFor);
final List<byte[]> 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<PrivacyGroup> findOnchainPrivacyGroup(
final PrivacyController privacyController,
final Optional<Bytes> maybePrivacyGroupId,
final String privacyUserId,
final PrivateTransaction privateTransaction) {
return maybePrivacyGroupId.flatMap(
privacyGroupId ->
privacyController.findOnchainPrivacyGroupAndAddNewMembers(
privacyGroupId, privacyUserId, privateTransaction));
}
public static Optional<PrivacyGroup> findOffchainPrivacyGroup(
final PrivacyController privacyController,
final Optional<Bytes> maybePrivacyGroupId,
final String privacyUserId) {
return maybePrivacyGroupId.flatMap(
privacyGroupId ->
privacyController.findOffchainPrivacyGroupByGroupId(
privacyGroupId.toBase64String(), privacyUserId));
}
}

@ -161,7 +161,8 @@ public class PrivateTransactionProcessor {
.build();
} else {
final Address to = transaction.getTo().get();
final Optional<Account> maybeContract = Optional.ofNullable(privateWorldState.get(to));
final Optional<Account> maybeContract =
Optional.ofNullable(mutablePrivateWorldStateUpdater.get(to));
initialFrame =
commonMessageFrameBuilder
.type(MessageFrame.Type.MESSAGE_CALL)

@ -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<ExecutedPrivateTransaction> 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<String> addresses,
@ -153,319 +105,17 @@ public class RestrictedDefaultPrivacyController implements PrivacyController {
}
@Override
public PrivacyGroup[] findOffchainPrivacyGroupByMembers(
public PrivacyGroup[] findPrivacyGroupByMembers(
final List<String> addresses, final String privacyUserId) {
return enclave.findPrivacyGroup(addresses);
}
@Override
public ValidationResult<TransactionInvalidReason> 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<String> groupMembers = Lists.asList(privateFrom, privateFor);
final List<PrivacyGroup> matchingGroups =
Lists.newArrayList(enclave.findPrivacyGroup(groupMembers));
final List<PrivacyGroup> 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<TransactionProcessingResult> simulatePrivateTransaction(
final String privacyGroupId,
final String privacyUserId,
final CallParameter callParams,
final long blockNumber) {
final Optional<TransactionProcessingResult> result =
privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber);
return result;
}
@Override
public Optional<String> buildAndSendAddPayload(
final PrivateTransaction privateTransaction,
final Bytes32 privacyGroupId,
final String privacyUserId) {
if (isGroupAdditionTransaction(privateTransaction)) {
final List<PrivateTransactionMetadata> privateTransactionMetadataList =
buildTransactionMetadataList(privacyGroupId);
if (privateTransactionMetadataList.size() > 0) {
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList =
retrievePrivateTransactions(
privacyGroupId, privateTransactionMetadataList, privacyUserId);
final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList);
final List<String> privateFor =
getParticipantsFromParameter(privateTransaction.getPayload());
return Optional.of(
enclave.send(bytes.toBase64String(), privacyUserId, privateFor).getKey());
}
}
return Optional.empty();
}
@Override
public Optional<PrivacyGroup> 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<PrivacyGroup> findOffchainPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId) {
return Optional.ofNullable(enclave.retrievePrivacyGroup(privacyGroupId));
}
@Override
public List<PrivacyGroup> findOnchainPrivacyGroupByMembers(
final List<String> addresses, final String privacyUserId) {
final ArrayList<PrivacyGroup> privacyGroups = new ArrayList<>();
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.empty());
privacyGroupHeadBlockMap
.keySet()
.forEach(
c -> {
final Optional<PrivacyGroup> maybePrivacyGroup =
findOnchainPrivacyGroupByGroupId(c, privacyUserId);
if (maybePrivacyGroup.isPresent()
&& maybePrivacyGroup.get().getMembers().containsAll(addresses)) {
privacyGroups.add(maybePrivacyGroup.get());
}
});
return privacyGroups;
}
public Optional<PrivacyGroup> findOnchainPrivacyGroupByGroupId(
final Bytes privacyGroupId, final String enclaveKey) {
// get the privateFor list from the management contract
final Optional<TransactionProcessingResult> 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<PrivacyGroup> findOnchainPrivacyGroupAndAddNewMembers(
final Bytes privacyGroupId,
final String privacyUserId,
final PrivateTransaction privateTransaction) {
// get the privateFor list from the management contract
final Optional<TransactionProcessingResult> privateTransactionSimulatorResultOptional =
privateTransactionSimulator.process(
privacyGroupId.toBase64String(), buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE));
final List<String> 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<String> 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<String> decodeList(final Bytes rlpEncodedList) {
final ArrayList<String> 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<String> getParticipantsFromParameter(final Bytes input) {
final List<String> 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<PrivateTransactionMetadata> buildTransactionMetadataList(
final Bytes privacyGroupId) {
final List<PrivateTransactionMetadata> 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<PrivateTransactionWithMetadata> retrievePrivateTransactions(
final Bytes32 privacyGroupId,
final List<PrivateTransactionMetadata> privateTransactionMetadataList,
final String privacyUserId) {
final ArrayList<PrivateTransactionWithMetadata> 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<Bytes> getContractCode(
final String privacyGroupId,
final Address contractAddress,
final Hash blockHash,
final String privacyUserId) {
return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress);
}
@Override
public List<PrivateTransactionWithMetadata> 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<PrivateTransactionWithMetadata> 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<TransactionProcessingResult> result =
privateTransactionSimulator.process(
privateTransaction.getPrivacyGroupId().get().toBase64String(),
buildCallParams(GET_VERSION_METHOD_SIGNATURE));
new VersionedPrivateTransaction(privateTransaction, result).writeTo(rlpOutput);
final List<String> 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<Long> blockNumber)
throws MultiTenancyValidationException {
// NO VALIDATION NEEDED
}
@Override
public PrivateTransactionSimulator getTransactionSimulator() {
return privateTransactionSimulator;
}
@Override
public Optional<Hash> 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()));
public void verifyPrivacyGroupContainsPrivacyUserId(
final String privacyGroupId, final String privacyUserId, final Optional<Long> blockNumber) {
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId);
}
}

@ -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> onchainPrivacyGroupContract;
public RestrictedMultiTenancyPrivacyController(
final PrivacyController privacyController,
final Optional<BigInteger> 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<BigInteger> chainId,
final Enclave enclave,
final Optional<OnchainPrivacyGroupContract> onchainPrivacyGroupContract) {
this.privacyController = privacyController;
this.enclave = enclave;
this.onchainPrivacyGroupContract = onchainPrivacyGroupContract;
privateTransactionValidator = new PrivateTransactionValidator(chainId);
}
@Override
public Optional<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash(
final Hash pmtHash, final String enclaveKey) {
return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey);
}
@Override
public String createPrivateMarkerTransactionPayload(
final PrivateTransaction privateTransaction,
final String privacyUserId,
final Optional<PrivacyGroup> 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<String> 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<String> 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<TransactionInvalidReason> 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<TransactionProcessingResult> 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<String> 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<PrivacyGroup> findOffchainPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId) {
final Optional<PrivacyGroup> maybePrivacyGroup =
privacyController.findOffchainPrivacyGroupByGroupId(privacyGroupId, privacyUserId);
checkGroupParticipation(maybePrivacyGroup, privacyUserId);
return maybePrivacyGroup;
}
@Override
public Optional<PrivacyGroup> findPrivacyGroupByGroupId(
final String privacyGroupId, final String privacyUserId) {
final Optional<PrivacyGroup> maybePrivacyGroup =
privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId);
checkGroupParticipation(maybePrivacyGroup, privacyUserId);
return maybePrivacyGroup;
}
private void checkGroupParticipation(
final Optional<PrivacyGroup> 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<PrivacyGroup> findOnchainPrivacyGroupByMembers(
final List<String> addresses, final String privacyUserId) {
if (!addresses.contains(privacyUserId)) {
throw new MultiTenancyValidationException(
"Privacy group addresses must contain the enclave public key");
}
final List<PrivacyGroup> resultantGroups =
privacyController.findOnchainPrivacyGroupByMembers(addresses, privacyUserId);
return resultantGroups.stream()
.filter(g -> g.getMembers().contains(privacyUserId))
.collect(Collectors.toList());
}
@Override
public List<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) {
return privacyController.retrieveAddBlob(addDataKey);
}
@Override
public boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) {
return privacyController.isGroupAdditionTransaction(privateTransaction);
}
@Override
public Optional<Bytes> 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<PrivacyGroup> findOnchainPrivacyGroupAndAddNewMembers(
final Bytes privacyGroupId,
final String privacyUserId,
final PrivateTransaction privateTransaction) {
final Optional<PrivacyGroup> 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<Long> 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<Long> blockNumber) {
final Optional<PrivacyGroup> 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<Hash> getStateRootByBlockNumber(
final String privacyGroupId, final String privacyUserId, final long blockNumber) {
verifyPrivacyGroupContainsPrivacyUserId(
privacyGroupId, privacyUserId, Optional.of(blockNumber));
return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber);
}
}

@ -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<Log> 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,

@ -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<Log> 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<String> 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);
}

@ -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<String> 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<SignatureAlgorithm> 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<PrivacyGroup> 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<TransactionInvalidReason> 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<TransactionInvalidReason> 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<PrivacyGroup> 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<PrivacyGroup> 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<Bytes32, Hash> 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> privacyGroupHeadBlockMap =
Optional.of(new PrivacyGroupHeadBlockMap(bytes32HashMap));
when(privateStateStorage.getPrivacyGroupHeadBlockMap(Hash.ZERO))
.thenReturn(privacyGroupHeadBlockMap);
final List<PrivateTransactionMetadata> 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<BlockHeader> 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));
}
}

@ -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<SignatureAlgorithm> 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<String> 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<Log> 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<String> 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<PrivacyGroup> 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);

Loading…
Cancel
Save