mirror of https://github.com/hyperledger/besu
Privacy multitenancy validation (#296)
Signed-off-by: Jason Frame <jasonwframe@gmail.com>pull/302/head
parent
ef61b54bba
commit
a7cde4dd8e
@ -0,0 +1,230 @@ |
|||||||
|
/* |
||||||
|
* 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.enclave.Enclave; |
||||||
|
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||||
|
import org.hyperledger.besu.enclave.types.PrivacyGroup.Type; |
||||||
|
import org.hyperledger.besu.enclave.types.ReceiveResponse; |
||||||
|
import org.hyperledger.besu.enclave.types.SendResponse; |
||||||
|
import org.hyperledger.besu.ethereum.core.Account; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.PrivacyParameters; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||||
|
import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory; |
||||||
|
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; |
||||||
|
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
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; |
||||||
|
|
||||||
|
public class DefaultPrivacyController implements PrivacyController { |
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
|
||||||
|
private final Enclave enclave; |
||||||
|
private final PrivateStateStorage privateStateStorage; |
||||||
|
private final WorldStateArchive privateWorldStateArchive; |
||||||
|
private final PrivateTransactionValidator privateTransactionValidator; |
||||||
|
private final PrivateMarkerTransactionFactory privateMarkerTransactionFactory; |
||||||
|
|
||||||
|
public DefaultPrivacyController( |
||||||
|
final PrivacyParameters privacyParameters, |
||||||
|
final Optional<BigInteger> chainId, |
||||||
|
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) { |
||||||
|
this( |
||||||
|
privacyParameters.getEnclave(), |
||||||
|
privacyParameters.getPrivateStateStorage(), |
||||||
|
privacyParameters.getPrivateWorldStateArchive(), |
||||||
|
new PrivateTransactionValidator(chainId), |
||||||
|
privateMarkerTransactionFactory); |
||||||
|
} |
||||||
|
|
||||||
|
public DefaultPrivacyController( |
||||||
|
final Enclave enclave, |
||||||
|
final PrivateStateStorage privateStateStorage, |
||||||
|
final WorldStateArchive privateWorldStateArchive, |
||||||
|
final PrivateTransactionValidator privateTransactionValidator, |
||||||
|
final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) { |
||||||
|
this.enclave = enclave; |
||||||
|
this.privateStateStorage = privateStateStorage; |
||||||
|
this.privateWorldStateArchive = privateWorldStateArchive; |
||||||
|
this.privateTransactionValidator = privateTransactionValidator; |
||||||
|
this.privateMarkerTransactionFactory = privateMarkerTransactionFactory; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SendTransactionResponse sendTransaction( |
||||||
|
final PrivateTransaction privateTransaction, final String enclavePublicKey) { |
||||||
|
try { |
||||||
|
LOG.trace("Storing private transaction in enclave"); |
||||||
|
final SendResponse sendResponse = sendRequest(privateTransaction, enclavePublicKey); |
||||||
|
final String enclaveKey = sendResponse.getKey(); |
||||||
|
if (privateTransaction.getPrivacyGroupId().isPresent()) { |
||||||
|
final String privacyGroupId = privateTransaction.getPrivacyGroupId().get().toBase64String(); |
||||||
|
return new SendTransactionResponse(enclaveKey, privacyGroupId); |
||||||
|
} else { |
||||||
|
final String privateFrom = privateTransaction.getPrivateFrom().toBase64String(); |
||||||
|
final String privacyGroupId = getPrivacyGroupId(enclaveKey, privateFrom); |
||||||
|
return new SendTransactionResponse(enclaveKey, privacyGroupId); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
LOG.error("Failed to store private transaction in enclave", e); |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ReceiveResponse retrieveTransaction( |
||||||
|
final String enclaveKey, final String enclavePublicKey) { |
||||||
|
return enclave.receive(enclaveKey, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PrivacyGroup createPrivacyGroup( |
||||||
|
final List<String> addresses, |
||||||
|
final String name, |
||||||
|
final String description, |
||||||
|
final String enclavePublicKey) { |
||||||
|
return enclave.createPrivacyGroup(addresses, enclavePublicKey, name, description); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String deletePrivacyGroup(final String privacyGroupId, final String enclavePublicKey) { |
||||||
|
return enclave.deletePrivacyGroup(privacyGroupId, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PrivacyGroup[] findPrivacyGroup( |
||||||
|
final List<String> addresses, final String enclavePublicKey) { |
||||||
|
return enclave.findPrivacyGroup(addresses); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Transaction createPrivacyMarkerTransaction( |
||||||
|
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { |
||||||
|
return privateMarkerTransactionFactory.create(transactionEnclaveKey, privateTransaction); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ValidationResult<TransactionValidator.TransactionInvalidReason> validatePrivateTransaction( |
||||||
|
final PrivateTransaction privateTransaction, |
||||||
|
final String privacyGroupId, |
||||||
|
final String enclavePublicKey) { |
||||||
|
return privateTransactionValidator.validate( |
||||||
|
privateTransaction, |
||||||
|
determineBesuNonce(privateTransaction.getSender(), privacyGroupId, enclavePublicKey)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long determineEeaNonce( |
||||||
|
final String privateFrom, |
||||||
|
final String[] privateFor, |
||||||
|
final Address address, |
||||||
|
final String enclavePublicKey) { |
||||||
|
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() == 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, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long determineBesuNonce( |
||||||
|
final Address sender, final String privacyGroupId, final String enclavePublicKey) { |
||||||
|
return privateStateStorage |
||||||
|
.getLatestStateRoot(Bytes.fromBase64String(privacyGroupId)) |
||||||
|
.map( |
||||||
|
lastRootHash -> |
||||||
|
privateWorldStateArchive |
||||||
|
.getMutable(lastRootHash) |
||||||
|
.map( |
||||||
|
worldState -> { |
||||||
|
final Account maybePrivateSender = worldState.get(sender); |
||||||
|
|
||||||
|
if (maybePrivateSender != null) { |
||||||
|
return maybePrivateSender.getNonce(); |
||||||
|
} |
||||||
|
// account has not interacted in this private state
|
||||||
|
return Account.DEFAULT_NONCE; |
||||||
|
}) |
||||||
|
// private state does not exist
|
||||||
|
.orElse(Account.DEFAULT_NONCE)) |
||||||
|
.orElse( |
||||||
|
// private state does not exist
|
||||||
|
Account.DEFAULT_NONCE); |
||||||
|
} |
||||||
|
|
||||||
|
private SendResponse sendRequest( |
||||||
|
final PrivateTransaction privateTransaction, final String enclavePublicKey) { |
||||||
|
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); |
||||||
|
privateTransaction.writeTo(rlpOutput); |
||||||
|
final String payload = rlpOutput.encoded().toBase64String(); |
||||||
|
|
||||||
|
if (privateTransaction.getPrivacyGroupId().isPresent()) { |
||||||
|
return enclave.send( |
||||||
|
payload, enclavePublicKey, privateTransaction.getPrivacyGroupId().get().toBase64String()); |
||||||
|
} else { |
||||||
|
final List<String> privateFor = |
||||||
|
privateTransaction.getPrivateFor().get().stream() |
||||||
|
.map(Bytes::toBase64String) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
|
||||||
|
if (privateFor.isEmpty()) { |
||||||
|
privateFor.add(privateTransaction.getPrivateFrom().toBase64String()); |
||||||
|
} |
||||||
|
return enclave.send( |
||||||
|
payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String getPrivacyGroupId(final String key, final String privateFrom) { |
||||||
|
LOG.debug("Getting privacy group for key {} and privateFrom {}", key, privateFrom); |
||||||
|
try { |
||||||
|
return enclave.receive(key, privateFrom).getPrivacyGroupId(); |
||||||
|
} catch (final RuntimeException e) { |
||||||
|
LOG.error("Failed to retrieve private transaction in enclave", e); |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
/* |
||||||
|
* 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.enclave.Enclave; |
||||||
|
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||||
|
import org.hyperledger.besu.enclave.types.ReceiveResponse; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class MultiTenancyPrivacyController implements PrivacyController { |
||||||
|
|
||||||
|
private final PrivacyController privacyController; |
||||||
|
private final Enclave enclave; |
||||||
|
|
||||||
|
public MultiTenancyPrivacyController( |
||||||
|
final PrivacyController privacyController, final Enclave enclave) { |
||||||
|
this.privacyController = privacyController; |
||||||
|
this.enclave = enclave; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SendTransactionResponse sendTransaction( |
||||||
|
final PrivateTransaction privateTransaction, final String enclavePublicKey) { |
||||||
|
verifyPrivateFromMatchesEnclavePublicKey( |
||||||
|
privateTransaction.getPrivateFrom().toBase64String(), enclavePublicKey); |
||||||
|
if (privateTransaction.getPrivacyGroupId().isPresent()) { |
||||||
|
verifyPrivacyGroupContainsEnclavePublicKey( |
||||||
|
privateTransaction.getPrivacyGroupId().get().toBase64String(), enclavePublicKey); |
||||||
|
} |
||||||
|
return privacyController.sendTransaction(privateTransaction, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ReceiveResponse retrieveTransaction( |
||||||
|
final String enclaveKey, final String enclavePublicKey) { |
||||||
|
// no validation necessary as the enclave receive only returns data for the enclave public key
|
||||||
|
return privacyController.retrieveTransaction(enclaveKey, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PrivacyGroup createPrivacyGroup( |
||||||
|
final List<String> addresses, |
||||||
|
final String name, |
||||||
|
final String description, |
||||||
|
final String enclavePublicKey) { |
||||||
|
// no validation necessary as the enclave createPrivacyGroup fails if the addresses don't
|
||||||
|
// include the from (enclavePublicKey)
|
||||||
|
return privacyController.createPrivacyGroup(addresses, name, description, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String deletePrivacyGroup(final String privacyGroupId, final String enclavePublicKey) { |
||||||
|
verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey); |
||||||
|
return privacyController.deletePrivacyGroup(privacyGroupId, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PrivacyGroup[] findPrivacyGroup( |
||||||
|
final List<String> addresses, final String enclavePublicKey) { |
||||||
|
if (!addresses.contains(enclavePublicKey)) { |
||||||
|
throw new MultiTenancyValidationException( |
||||||
|
"Privacy group addresses must contain the enclave public key"); |
||||||
|
} |
||||||
|
return privacyController.findPrivacyGroup(addresses, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Transaction createPrivacyMarkerTransaction( |
||||||
|
final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { |
||||||
|
return privacyController.createPrivacyMarkerTransaction( |
||||||
|
transactionEnclaveKey, privateTransaction); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ValidationResult<TransactionInvalidReason> validatePrivateTransaction( |
||||||
|
final PrivateTransaction privateTransaction, |
||||||
|
final String privacyGroupId, |
||||||
|
final String enclavePublicKey) { |
||||||
|
return privacyController.validatePrivateTransaction( |
||||||
|
privateTransaction, privacyGroupId, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long determineEeaNonce( |
||||||
|
final String privateFrom, |
||||||
|
final String[] privateFor, |
||||||
|
final Address address, |
||||||
|
final String enclavePublicKey) { |
||||||
|
verifyPrivateFromMatchesEnclavePublicKey(privateFrom, enclavePublicKey); |
||||||
|
return privacyController.determineEeaNonce(privateFrom, privateFor, address, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long determineBesuNonce( |
||||||
|
final Address sender, final String privacyGroupId, final String enclavePublicKey) { |
||||||
|
verifyPrivacyGroupContainsEnclavePublicKey(privacyGroupId, enclavePublicKey); |
||||||
|
return privacyController.determineBesuNonce(sender, privacyGroupId, enclavePublicKey); |
||||||
|
} |
||||||
|
|
||||||
|
private void verifyPrivateFromMatchesEnclavePublicKey( |
||||||
|
final String privateFrom, final String enclavePublicKey) { |
||||||
|
if (!privateFrom.equals(enclavePublicKey)) { |
||||||
|
throw new MultiTenancyValidationException( |
||||||
|
"Transaction privateFrom must match enclave public key"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void verifyPrivacyGroupContainsEnclavePublicKey( |
||||||
|
final String privacyGroupId, final String enclavePublicKey) { |
||||||
|
final PrivacyGroup privacyGroup = enclave.retrievePrivacyGroup(privacyGroupId); |
||||||
|
if (!privacyGroup.getMembers().contains(enclavePublicKey)) { |
||||||
|
throw new MultiTenancyValidationException( |
||||||
|
"Privacy group must contain the enclave public key"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright ConsenSys AG. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.privacy; |
||||||
|
|
||||||
|
public class MultiTenancyValidationException extends RuntimeException { |
||||||
|
|
||||||
|
public MultiTenancyValidationException(final String message) { |
||||||
|
super(message); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,313 @@ |
|||||||
|
/* |
||||||
|
* 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 org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||||
|
import static org.mockito.Mockito.never; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.enclave.Enclave; |
||||||
|
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||||
|
import org.hyperledger.besu.enclave.types.PrivacyGroup.Type; |
||||||
|
import org.hyperledger.besu.enclave.types.ReceiveResponse; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class MultiTenancyPrivacyControllerTest { |
||||||
|
|
||||||
|
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 String ENCLAVE_KEY = "Ko2bVqD"; |
||||||
|
|
||||||
|
@Mock private PrivacyController privacyController; |
||||||
|
@Mock private Enclave enclave; |
||||||
|
|
||||||
|
private MultiTenancyPrivacyController multiTenancyPrivacyController; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
multiTenancyPrivacyController = new MultiTenancyPrivacyController(privacyController, enclave); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void |
||||||
|
sendsEeaTransactionWithMatchingPrivateFromAndEnclavePublicKeyAndProducesSuccessfulResponse() { |
||||||
|
final PrivateTransaction transaction = |
||||||
|
PrivateTransaction.builder() |
||||||
|
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID)); |
||||||
|
|
||||||
|
final SendTransactionResponse response = |
||||||
|
multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY); |
||||||
|
assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); |
||||||
|
verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void |
||||||
|
sendsBesuTransactionWithEnclavePublicKeyInPrivacyGroupAndProducesSuccessfulResponse() { |
||||||
|
final PrivateTransaction transaction = |
||||||
|
PrivateTransaction.builder() |
||||||
|
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
when(privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(new SendTransactionResponse(ENCLAVE_KEY, PRIVACY_GROUP_ID)); |
||||||
|
final PrivacyGroup privacyGroupWithEnclavePublicKey = |
||||||
|
new PrivacyGroup( |
||||||
|
PRIVACY_GROUP_ID, |
||||||
|
Type.PANTHEON, |
||||||
|
"", |
||||||
|
"", |
||||||
|
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithEnclavePublicKey); |
||||||
|
|
||||||
|
final SendTransactionResponse response = |
||||||
|
multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(response.getEnclaveKey()).isEqualTo(ENCLAVE_KEY); |
||||||
|
assertThat(response.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); |
||||||
|
verify(privacyController).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
verify(enclave).retrievePrivacyGroup(PRIVACY_GROUP_ID); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void sendEeaTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() { |
||||||
|
final PrivateTransaction transaction = |
||||||
|
PrivateTransaction.builder() |
||||||
|
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Transaction privateFrom must match enclave public key"); |
||||||
|
|
||||||
|
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void sendBesuTransactionFailsWithValidationExceptionWhenPrivateFromDoesNotMatch() { |
||||||
|
final PrivateTransaction transaction = |
||||||
|
PrivateTransaction.builder() |
||||||
|
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY2)) |
||||||
|
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Transaction privateFrom must match enclave public key"); |
||||||
|
|
||||||
|
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void |
||||||
|
sendBesuTransactionFailsWithValidationExceptionWhenPrivacyGroupDoesNotContainEnclavePublicKey() { |
||||||
|
final PrivateTransaction transaction = |
||||||
|
PrivateTransaction.builder() |
||||||
|
.privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) |
||||||
|
.build(); |
||||||
|
|
||||||
|
final PrivacyGroup privacyGroupWithoutEnclavePublicKey = |
||||||
|
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithoutEnclavePublicKey); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> multiTenancyPrivacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Privacy group must contain the enclave public key"); |
||||||
|
|
||||||
|
verify(privacyController, never()).sendTransaction(transaction, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@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); |
||||||
|
final PrivacyGroup delegatePrivacyGroup = |
||||||
|
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "name", "description", addresses); |
||||||
|
|
||||||
|
when(privacyController.createPrivacyGroup( |
||||||
|
addresses, "name", "description", ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(delegatePrivacyGroup); |
||||||
|
|
||||||
|
final PrivacyGroup privacyGroup = |
||||||
|
multiTenancyPrivacyController.createPrivacyGroup( |
||||||
|
addresses, "name", "description", ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(privacyGroup).isEqualToComparingFieldByField(delegatePrivacyGroup); |
||||||
|
verify(privacyController) |
||||||
|
.createPrivacyGroup(addresses, "name", "description", ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void deletesPrivacyGroupWhenEnclavePublicKeyInPrivacyGroup() { |
||||||
|
final PrivacyGroup privacyGroupWithEnclavePublicKey = |
||||||
|
new PrivacyGroup( |
||||||
|
PRIVACY_GROUP_ID, |
||||||
|
Type.PANTHEON, |
||||||
|
"", |
||||||
|
"", |
||||||
|
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithEnclavePublicKey); |
||||||
|
when(privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(ENCLAVE_PUBLIC_KEY1); |
||||||
|
|
||||||
|
final String privacyGroupId = |
||||||
|
multiTenancyPrivacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(privacyGroupId).isEqualTo(ENCLAVE_PUBLIC_KEY1); |
||||||
|
verify(privacyController).deletePrivacyGroup(PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void |
||||||
|
deletePrivacyGroupFailsWithValidationExceptionWhenPrivacyGroupDoesNotContainEnclavePublicKey() { |
||||||
|
final PrivacyGroup privacyGroupWithoutEnclavePublicKey = |
||||||
|
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithoutEnclavePublicKey); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> |
||||||
|
multiTenancyPrivacyController.deletePrivacyGroup( |
||||||
|
PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Privacy group must contain the enclave public key"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void findsPrivacyGroupWhenEnclavePublicKeyInAddresses() { |
||||||
|
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2); |
||||||
|
final PrivacyGroup privacyGroup = |
||||||
|
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(privacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(new PrivacyGroup[] {privacyGroup}); |
||||||
|
|
||||||
|
final PrivacyGroup[] privacyGroups = |
||||||
|
multiTenancyPrivacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(privacyGroups).hasSize(1); |
||||||
|
assertThat(privacyGroups[0]).isEqualToComparingFieldByField(privacyGroup); |
||||||
|
verify(privacyController).findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void findPrivacyGroupFailsWithValidationExceptionWhenEnclavePublicKeyNotInAddresses() { |
||||||
|
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY2); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> multiTenancyPrivacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Privacy group addresses must contain the enclave public key"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void determinesEeaNonceWhenPrivateFromMatchesEnclavePublicKey() { |
||||||
|
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 |
||||||
|
determineEeaNonceFailsWithValidationExceptionWhenPrivateFromDoesNotMatchEnclavePublicKey() { |
||||||
|
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 determineBesuNonceWhenEnclavePublicKeyInPrivacyGroup() { |
||||||
|
final PrivacyGroup privacyGroupWithEnclavePublicKey = |
||||||
|
new PrivacyGroup( |
||||||
|
PRIVACY_GROUP_ID, |
||||||
|
Type.PANTHEON, |
||||||
|
"", |
||||||
|
"", |
||||||
|
List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithEnclavePublicKey); |
||||||
|
when(privacyController.determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.thenReturn(10L); |
||||||
|
|
||||||
|
final long nonce = |
||||||
|
multiTenancyPrivacyController.determineBesuNonce( |
||||||
|
Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); |
||||||
|
assertThat(nonce).isEqualTo(10); |
||||||
|
verify(privacyController) |
||||||
|
.determineBesuNonce(Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void |
||||||
|
determineBesuNonceFailsWithValidationExceptionWhenEnclavePublicKeyNotInPrivacyGroup() { |
||||||
|
final PrivacyGroup privacyGroupWithoutEnclavePublicKey = |
||||||
|
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); |
||||||
|
when(enclave.retrievePrivacyGroup(PRIVACY_GROUP_ID)) |
||||||
|
.thenReturn(privacyGroupWithoutEnclavePublicKey); |
||||||
|
|
||||||
|
assertThatThrownBy( |
||||||
|
() -> |
||||||
|
multiTenancyPrivacyController.determineBesuNonce( |
||||||
|
Address.ZERO, PRIVACY_GROUP_ID, ENCLAVE_PUBLIC_KEY1)) |
||||||
|
.isInstanceOf(MultiTenancyValidationException.class) |
||||||
|
.hasMessage("Privacy group must contain the enclave public key"); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue