mirror of https://github.com/hyperledger/besu
Add ATs for on chain privacy with multi tenancy (#1422)
* add ATs for on-chain privacy with multi-tenancy Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>pull/1427/head
parent
5c0ee9d846
commit
8f80612374
@ -0,0 +1,42 @@ |
||||
/* |
||||
* 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.tests.acceptance.dsl.privacy.condition; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; |
||||
|
||||
import org.web3j.protocol.exceptions.ClientConnectionException; |
||||
|
||||
public class ExpectUnauthorizedPrivateTransactionReceipt implements PrivateCondition { |
||||
private final PrivacyTransactions transactions; |
||||
private final String transactionHash; |
||||
|
||||
public ExpectUnauthorizedPrivateTransactionReceipt( |
||||
final PrivacyTransactions transactions, final String transactionHash) { |
||||
this.transactions = transactions; |
||||
this.transactionHash = transactionHash; |
||||
} |
||||
|
||||
@Override |
||||
public void verify(final PrivacyNode node) { |
||||
try { |
||||
node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); |
||||
} catch (final ClientConnectionException e) { |
||||
assertThat(e.getMessage()).contains("Unauthorized"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
/* |
||||
* 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.tests.acceptance.dsl.transaction.privacy; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Hash; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
public class PrivGetTransaction |
||||
implements Transaction<PrivacyRequestFactory.GetPrivateTransactionResponse> { |
||||
|
||||
private final String transactionHash; |
||||
|
||||
public PrivGetTransaction(final String transactionHash) { |
||||
this.transactionHash = transactionHash; |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyRequestFactory.GetPrivateTransactionResponse execute(final NodeRequests node) { |
||||
try { |
||||
final PrivacyRequestFactory.GetPrivateTransactionResponse response = |
||||
node.privacy().privGetPrivateTransaction(Hash.fromHexString(transactionHash)).send(); |
||||
assertThat(response).as("check response is not null").isNotNull(); |
||||
assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); |
||||
return response; |
||||
} catch (final IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* 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.tests.acceptance.privacy.multitenancy; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public class MultiTenancyPrivacyGroup { |
||||
|
||||
private final Map<MultiTenancyPrivacyNode, List<String>> map; |
||||
|
||||
public MultiTenancyPrivacyGroup() { |
||||
this.map = new HashMap<>(); |
||||
} |
||||
|
||||
public MultiTenancyPrivacyGroup addNodeWithTenants( |
||||
final MultiTenancyPrivacyNode privacyNode, final List<String> tenants) { |
||||
map.put(privacyNode, tenants); |
||||
return this; |
||||
} |
||||
|
||||
public List<MultiTenancyPrivacyNode> getPrivacyNodes() { |
||||
return map.keySet().stream().collect(Collectors.toList()); |
||||
} |
||||
|
||||
public List<String> getTenantsForNode(final MultiTenancyPrivacyNode privacyNode) { |
||||
return map.get(privacyNode); |
||||
} |
||||
|
||||
public List<String> getTenants() { |
||||
return map.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); |
||||
} |
||||
|
||||
public PrivacyNode getGroupCreatingPrivacyNode() { |
||||
return getPrivacyNodes().get(0).getPrivacyNode(); |
||||
} |
||||
|
||||
public String getGroupCreatingTenant() { |
||||
return getPrivacyNodes().get(0).getTenants().get(0); |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
/* |
||||
* 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.tests.acceptance.privacy.multitenancy; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public class MultiTenancyPrivacyNode { |
||||
|
||||
private final PrivacyNode privacyNode; |
||||
private final Map<String, String> tenantToTokenMap; |
||||
|
||||
public MultiTenancyPrivacyNode(final PrivacyNode privacyNode) { |
||||
this.privacyNode = privacyNode; |
||||
this.tenantToTokenMap = new HashMap<>(); |
||||
} |
||||
|
||||
public MultiTenancyPrivacyNode addTenantWithToken(final String tenant, final String token) { |
||||
tenantToTokenMap.put(tenant, token); |
||||
return this; |
||||
} |
||||
|
||||
public List<String> getTenants() { |
||||
return tenantToTokenMap.keySet().stream().collect(Collectors.toList()); |
||||
} |
||||
|
||||
public String getTokenForTenant(final String tenant) { |
||||
return tenantToTokenMap.get(tenant); |
||||
} |
||||
|
||||
public PrivacyNode getPrivacyNode() { |
||||
return privacyNode; |
||||
} |
||||
} |
@ -0,0 +1,461 @@ |
||||
/* |
||||
* 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.tests.acceptance.privacy.multitenancy; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.contract.CallPrivateSmartContractFunction; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.CreateOnChainPrivacyGroupTransaction; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; |
||||
import org.hyperledger.besu.tests.web3j.generated.EventEmitter; |
||||
import org.hyperledger.besu.tests.web3j.privacy.OnChainPrivacyAcceptanceTestBase; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.web3j.crypto.Credentials; |
||||
import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; |
||||
import org.web3j.protocol.core.methods.response.EthCall; |
||||
import org.web3j.utils.Base64String; |
||||
|
||||
public class OnChainMultiTenancyAcceptanceTest extends OnChainPrivacyAcceptanceTestBase { |
||||
|
||||
private static final String eventEmitterDeployed = |
||||
"0x6080604052600436106100565763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fa4f245811461005b5780636057361d1461008257806367e404ce146100ae575b600080fd5b34801561006757600080fd5b506100706100ec565b60408051918252519081900360200190f35b34801561008e57600080fd5b506100ac600480360360208110156100a557600080fd5b50356100f2565b005b3480156100ba57600080fd5b506100c3610151565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60025490565b604080513381526020810183905281517fc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5929181900390910190a16002556001805473ffffffffffffffffffffffffffffffffffffffff191633179055565b60015473ffffffffffffffffffffffffffffffffffffffff169056fea165627a7a72305820c7f729cb24e05c221f5aa913700793994656f233fe2ce3b9fd9a505ea17e8d8a0029"; |
||||
|
||||
private static final PermissioningTransactions permissioningTransactions = |
||||
new PermissioningTransactions(); |
||||
private static final long VALUE_SET = 10L; |
||||
|
||||
private PrivacyNode alice; |
||||
private MultiTenancyPrivacyNode aliceMultiTenancyPrivacyNode; |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
alice = |
||||
privacyBesu.createOnChainPrivacyGroupEnabledMinerNode( |
||||
"node1", PrivacyAccountResolver.MULTI_TENANCY, Address.PRIVACY, true); |
||||
final BesuNode aliceBesu = alice.getBesu(); |
||||
privacyCluster.startNodes(alice); |
||||
final String alice1Token = |
||||
aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); |
||||
aliceBesu.useAuthenticationTokenInHeaderForJsonRpc(alice1Token); |
||||
final String alice2Token = |
||||
aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user2", "Password2")); |
||||
final String alice3Token = |
||||
aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user3", "Password3")); |
||||
privacyCluster.awaitPeerCount(alice); |
||||
|
||||
final String alice1EnclaveKey = alice.getOrion().getPublicKeys().get(0); |
||||
final String alice2EnclaveKey = alice.getOrion().getPublicKeys().get(1); |
||||
final String alice3EnclaveKey = alice.getOrion().getPublicKeys().get(2); |
||||
|
||||
aliceMultiTenancyPrivacyNode = new MultiTenancyPrivacyNode(alice); |
||||
aliceMultiTenancyPrivacyNode |
||||
.addTenantWithToken(alice1EnclaveKey, alice1Token) |
||||
.addTenantWithToken(alice2EnclaveKey, alice2Token) |
||||
.addTenantWithToken(alice3EnclaveKey, alice3Token); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() { |
||||
privacyCluster.close(); |
||||
} |
||||
|
||||
@Test |
||||
public void createPrivacyGroup() { |
||||
createOnChainPrivacyGroup(alice); |
||||
} |
||||
|
||||
@Test |
||||
public void createPrivacyGroupWithAllTenants() { |
||||
final MultiTenancyPrivacyGroup privacyGroup = new MultiTenancyPrivacyGroup(); |
||||
privacyGroup.addNodeWithTenants( |
||||
aliceMultiTenancyPrivacyNode, aliceMultiTenancyPrivacyNode.getTenants()); |
||||
createOnChainPrivacyGroup(privacyGroup); |
||||
} |
||||
|
||||
@Test |
||||
public void noAccessWhenNotAMember() { |
||||
final MultiTenancyPrivacyGroup twoTenantsFromAlice = new MultiTenancyPrivacyGroup(); |
||||
final List<String> tenants = aliceMultiTenancyPrivacyNode.getTenants(); |
||||
final String removedTenant = tenants.remove(tenants.size() - 1); |
||||
twoTenantsFromAlice.addNodeWithTenants(aliceMultiTenancyPrivacyNode, tenants); |
||||
final String privacyGroupId = createOnChainPrivacyGroup(twoTenantsFromAlice); |
||||
|
||||
final MultiTenancyPrivacyNode multiTenancyPrivacyNode = |
||||
twoTenantsFromAlice.getPrivacyNodes().get(0); |
||||
final String tenant = tenants.get(0); |
||||
final PrivacyNode privacyNode = multiTenancyPrivacyNode.getPrivacyNode(); |
||||
final BesuNode privacyNodeBesu = privacyNode.getBesu(); |
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final EventEmitter eventEmitter = |
||||
privacyNode.execute( |
||||
privateContractTransactions.createSmartContractWithPrivacyGroupId( |
||||
EventEmitter.class, |
||||
privacyNode.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
tenant, |
||||
privacyGroupId)); |
||||
|
||||
final String transactionHash = getContractDeploymentCommitmentHash(eventEmitter); |
||||
|
||||
// check that a member can get the transaction receipt
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
privacyNode.verify( |
||||
privateTransactionVerifier.validPrivateTransactionReceipt( |
||||
transactionHash, |
||||
(PrivateTransactionReceipt) eventEmitter.getTransactionReceipt().get())); |
||||
assertThat( |
||||
privacyNode |
||||
.execute( |
||||
privacyTransactions.privGetCode( |
||||
privacyGroupId, |
||||
Address.fromHexString(eventEmitter.getContractAddress()), |
||||
"latest")) |
||||
.toHexString()) |
||||
.isEqualTo(eventEmitterDeployed); |
||||
|
||||
// check that getting the transaction receipt does not work if you are not a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
privacyNode.verify( |
||||
privateTransactionVerifier.noPrivateTransactionReceipt( |
||||
transactionHash)); // returning null because the RPC is using the enclave key
|
||||
|
||||
// check that getting the code of the event emitter does not work when you are not a member
|
||||
assertThatThrownBy( |
||||
() -> |
||||
privacyNode.execute( |
||||
privacyTransactions.privGetCode( |
||||
privacyGroupId, |
||||
Address.fromHexString(eventEmitter.getContractAddress()), |
||||
"latest"))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
|
||||
final LogFilterJsonParameter filterParameter = |
||||
new LogFilterJsonParameter( |
||||
"earliest", |
||||
"latest", |
||||
List.of(eventEmitter.getContractAddress()), |
||||
Collections.emptyList(), |
||||
null); |
||||
|
||||
// create a valid filter
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final String filterId = |
||||
privacyNode.execute(privacyTransactions.newFilter(privacyGroupId, filterParameter)); |
||||
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final CallPrivateSmartContractFunction storeTransaction = |
||||
privateContractTransactions.callSmartContractWithPrivacyGroupId( |
||||
eventEmitter.getContractAddress(), |
||||
eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), |
||||
privacyNode.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
tenant, |
||||
privacyGroupId); |
||||
final String storeTransactionHash = privacyNode.execute(storeTransaction); |
||||
|
||||
privacyNode.execute(privacyTransactions.getPrivateTransactionReceipt(storeTransactionHash)); |
||||
|
||||
// check that getting the filter changes works for a member
|
||||
assertThat(privacyNode.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) |
||||
.hasSize(1); |
||||
|
||||
// check that getting the filter changes does not work if you are not a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> |
||||
privacyNode.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
|
||||
// check that getting the filter logs works for a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
assertThat(privacyNode.execute(privacyTransactions.getFilterLogs(privacyGroupId, filterId))) |
||||
.hasSize(3); // create privacy group, deploy event emitter, store on event emitter
|
||||
|
||||
// check that getting the filter logs does not work if you are not a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> privacyNode.execute(privacyTransactions.getFilterLogs(privacyGroupId, filterId))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
|
||||
// check that getting the logs works for a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
assertThat( |
||||
privacyNode.execute(privacyTransactions.privGetLogs(privacyGroupId, filterParameter))) |
||||
.hasSize(3); // create privacy group, deploy event emitter, store on event emitter
|
||||
|
||||
// check that getting the logs does not work if you are not a member
|
||||
privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> |
||||
privacyNode.execute( |
||||
privacyTransactions.privGetLogs(privacyGroupId, filterParameter))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
|
||||
final List<Base64String> base64StringList = |
||||
tenants.stream().map(Base64String::wrap).collect(Collectors.toList()); |
||||
|
||||
// check that a member can find the on-chain privacy group
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final List<PrivacyRequestFactory.OnChainPrivacyGroup> group = |
||||
privacyNode.execute( |
||||
privacyTransactions.findOnChainPrivacyGroup(Base64String.unwrapList(base64StringList))); |
||||
assertThat(group.size()).isEqualTo(1); |
||||
assertThat(group.get(0).getMembers()).containsAll(base64StringList).hasSize(2); |
||||
|
||||
// check that when you are not a member you cannot find the privacy group
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> |
||||
privacyNode.execute( |
||||
privacyTransactions.findOnChainPrivacyGroup( |
||||
Base64String.unwrapList(base64StringList)))) |
||||
.hasMessageContaining("Error finding onchain privacy group"); |
||||
|
||||
// check that a member can do a priv_call
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final EthCall readValue = |
||||
privacyNode.execute( |
||||
privacyTransactions.privCall( |
||||
privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())); |
||||
assertThat(new BigInteger(readValue.getValue().substring(2), 16)) |
||||
.isEqualByComparingTo(BigInteger.valueOf(VALUE_SET)); |
||||
|
||||
// check that when you are not a member you cannot do a priv_call
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> |
||||
privacyNode.execute( |
||||
privacyTransactions.privCall( |
||||
privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall()))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
|
||||
// check that a member can do a priv_getTransaction
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenant)); |
||||
final PrivacyRequestFactory.GetPrivateTransactionResponse privTransaction = |
||||
privacyNode.execute(privacyTransactions.privGetTransaction(storeTransactionHash)); |
||||
assertThat(privTransaction.getResult().getPrivacyGroupId()).isEqualTo(privacyGroupId); |
||||
|
||||
// check that when you are not a member you cannot do a priv_getTransaction
|
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); |
||||
assertThatThrownBy( |
||||
() -> privacyNode.execute(privacyTransactions.privGetTransaction(storeTransactionHash))) |
||||
.hasMessageContaining( |
||||
"Expecting actual not to be null"); // TODO: returning null because the RPC is using the
|
||||
// enclave key
|
||||
} |
||||
|
||||
@Test |
||||
public void removedMemberCannotGetFilterChanges() { |
||||
final MultiTenancyPrivacyGroup allTenantsFromAlice = new MultiTenancyPrivacyGroup(); |
||||
final List<String> tenants = aliceMultiTenancyPrivacyNode.getTenants(); |
||||
allTenantsFromAlice.addNodeWithTenants(aliceMultiTenancyPrivacyNode, tenants); |
||||
final String privacyGroupId = createOnChainPrivacyGroup(allTenantsFromAlice); |
||||
final MultiTenancyPrivacyNode multiTenancyPrivacyNode = |
||||
allTenantsFromAlice.getPrivacyNodes().get(0); |
||||
final String groupCreatingTenant = allTenantsFromAlice.getGroupCreatingTenant(); |
||||
final String tenantToBeRemoved = |
||||
tenants.stream().filter(t -> !t.equals(groupCreatingTenant)).findFirst().orElseThrow(); |
||||
final PrivacyNode groupCreatingPrivacyNode = allTenantsFromAlice.getGroupCreatingPrivacyNode(); |
||||
final BesuNode groupCreatingPrivacyNodeBesu = groupCreatingPrivacyNode.getBesu(); |
||||
groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(groupCreatingTenant)); |
||||
|
||||
final EventEmitter eventEmitter = |
||||
groupCreatingPrivacyNode.execute( |
||||
privateContractTransactions.createSmartContractWithPrivacyGroupId( |
||||
EventEmitter.class, |
||||
groupCreatingPrivacyNode.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
groupCreatingTenant, |
||||
privacyGroupId)); |
||||
|
||||
final LogFilterJsonParameter filterParameter = |
||||
new LogFilterJsonParameter( |
||||
"earliest", |
||||
"latest", |
||||
List.of(eventEmitter.getContractAddress()), |
||||
Collections.emptyList(), |
||||
null); |
||||
|
||||
final String filterId = |
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.newFilter(privacyGroupId, filterParameter)); |
||||
|
||||
final CallPrivateSmartContractFunction storeTransaction = |
||||
privateContractTransactions.callSmartContractWithPrivacyGroupId( |
||||
eventEmitter.getContractAddress(), |
||||
eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), |
||||
groupCreatingPrivacyNode.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
groupCreatingTenant, |
||||
privacyGroupId); |
||||
final String storeTransactionHash = groupCreatingPrivacyNode.execute(storeTransaction); |
||||
|
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.getPrivateTransactionReceipt(storeTransactionHash)); |
||||
|
||||
// check that getting the filter changes works for a member
|
||||
groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenantToBeRemoved)); |
||||
|
||||
assertThat( |
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.getFilterChanges(privacyGroupId, filterId))) |
||||
.hasSize(1); |
||||
|
||||
groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(groupCreatingTenant)); |
||||
final CallPrivateSmartContractFunction store2Transaction = |
||||
privateContractTransactions.callSmartContractWithPrivacyGroupId( |
||||
eventEmitter.getContractAddress(), |
||||
eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), |
||||
groupCreatingPrivacyNode.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
groupCreatingTenant, |
||||
privacyGroupId); |
||||
final String store2TransactionHash = groupCreatingPrivacyNode.execute(store2Transaction); |
||||
|
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.getPrivateTransactionReceipt(store2TransactionHash)); |
||||
|
||||
// now remove from privacy group
|
||||
final String removeTransactionHash = |
||||
removeFromPrivacyGroup( |
||||
privacyGroupId, |
||||
groupCreatingPrivacyNode, |
||||
groupCreatingTenant, |
||||
Credentials.create(groupCreatingPrivacyNode.getTransactionSigningKey()), |
||||
tenantToBeRemoved); |
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.getPrivateTransactionReceipt(removeTransactionHash)); |
||||
|
||||
// check that it does not work anymore when member has been removed
|
||||
groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( |
||||
multiTenancyPrivacyNode.getTokenForTenant(tenantToBeRemoved)); |
||||
assertThatThrownBy( |
||||
() -> |
||||
groupCreatingPrivacyNode.execute( |
||||
privacyTransactions.getFilterChanges(privacyGroupId, filterId))) |
||||
.hasMessageContaining("Unauthorized"); |
||||
} |
||||
|
||||
private String createOnChainPrivacyGroup(final MultiTenancyPrivacyGroup group) { |
||||
final List<MultiTenancyPrivacyNode> multiTenancyPrivacyNodes = group.getPrivacyNodes(); |
||||
final MultiTenancyPrivacyNode groupCreatorMultiTenancyPrivacyNode = |
||||
multiTenancyPrivacyNodes.get(0); |
||||
final PrivacyNode groupCreatorNode = group.getGroupCreatingPrivacyNode(); |
||||
final String groupCreatorTenant = group.getGroupCreatingTenant(); |
||||
final List<String> members = group.getTenants(); |
||||
final String token = groupCreatorMultiTenancyPrivacyNode.getTokenForTenant(groupCreatorTenant); |
||||
final CreateOnChainPrivacyGroupTransaction createTx = |
||||
privacyTransactions.createOnChainPrivacyGroup( |
||||
groupCreatorNode, groupCreatorTenant, members, token); |
||||
|
||||
final PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse createResponse = |
||||
groupCreatorNode.execute(createTx); |
||||
final String privacyGroupId = createResponse.getPrivacyGroupId(); |
||||
|
||||
final List<Base64String> base64StringList = |
||||
members.stream().map(Base64String::wrap).collect(Collectors.toList()); |
||||
for (final MultiTenancyPrivacyNode mtpn : multiTenancyPrivacyNodes) { |
||||
final PrivacyNode privacyNode = mtpn.getPrivacyNode(); |
||||
for (final String tenant : mtpn.getTenants()) { |
||||
if (members.contains(tenant)) { |
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc(mtpn.getTokenForTenant(tenant)); |
||||
privacyNode.verify(onChainPrivacyGroupExists(privacyGroupId, base64StringList)); |
||||
} |
||||
} |
||||
} |
||||
groupCreatorNode.getBesu().useAuthenticationTokenInHeaderForJsonRpc(token); |
||||
final String commitmentHash = |
||||
callGetParticipantsMethodAndReturnCommitmentHash( |
||||
privacyGroupId, groupCreatorNode, groupCreatorTenant); |
||||
final PrivateTransactionReceipt expectedReceipt = |
||||
buildExpectedAddMemberTransactionReceipt( |
||||
privacyGroupId, groupCreatorNode, groupCreatorTenant, members.toArray(new String[] {})); |
||||
|
||||
for (final MultiTenancyPrivacyNode mtpn : multiTenancyPrivacyNodes) { |
||||
final PrivacyNode privacyNode = mtpn.getPrivacyNode(); |
||||
for (final String tenant : mtpn.getTenants()) { |
||||
if (members.contains(tenant)) { |
||||
privacyNode |
||||
.getBesu() |
||||
.useAuthenticationTokenInHeaderForJsonRpc(mtpn.getTokenForTenant(tenant)); |
||||
privacyNode.verify( |
||||
privateTransactionVerifier.validPrivateTransactionReceipt( |
||||
commitmentHash, expectedReceipt)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return privacyGroupId; |
||||
} |
||||
|
||||
private String removeFromPrivacyGroup( |
||||
final String privacyGroupId, |
||||
final PrivacyNode node, |
||||
final String nodeRemovingMember, |
||||
final Credentials signer, |
||||
final String memberBeingRemoved) { |
||||
return node.execute( |
||||
privacyTransactions.removeFromPrivacyGroup( |
||||
privacyGroupId, nodeRemovingMember, signer, memberBeingRemoved)); |
||||
} |
||||
} |
@ -0,0 +1,177 @@ |
||||
/* |
||||
* 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.tests.web3j.privacy; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement.GET_PARTICIPANTS_METHOD_SIGNATURE; |
||||
|
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; |
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.ExpectValidOnChainPrivacyGroupCreated; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.CreateOnChainPrivacyGroupTransaction; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; |
||||
|
||||
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.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; |
||||
import org.web3j.protocol.core.methods.response.TransactionReceipt; |
||||
import org.web3j.tx.Contract; |
||||
import org.web3j.utils.Base64String; |
||||
|
||||
public class OnChainPrivacyAcceptanceTestBase extends PrivacyAcceptanceTestBase { |
||||
|
||||
protected String createOnChainPrivacyGroup(final PrivacyNode... members) { |
||||
final List<String> addresses = |
||||
Arrays.asList(members).stream().map(m -> m.getEnclaveKey()).collect(Collectors.toList()); |
||||
return createOnChainPrivacyGroup(members[0].getEnclaveKey(), addresses, members); |
||||
} |
||||
|
||||
/** |
||||
* Crete an onchain privacy group. The privacy group id will be randomly generated. |
||||
* |
||||
* <p>This method also checks that each node member has successfully processed the transaction and |
||||
* has the expected list of member for the group. |
||||
* |
||||
* @param members the list of members of the privacy group. The first member of the list will be |
||||
* the creator of the group. |
||||
* @return the id of the privacy group |
||||
*/ |
||||
protected String createOnChainPrivacyGroup( |
||||
final String privateFrom, final List<String> addresses, final PrivacyNode... members) { |
||||
|
||||
final PrivacyNode groupCreator = members[0]; |
||||
|
||||
final CreateOnChainPrivacyGroupTransaction createTx = |
||||
privacyTransactions.createOnChainPrivacyGroup(groupCreator, privateFrom, addresses); |
||||
|
||||
final PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse createResponse = |
||||
groupCreator.execute(createTx); |
||||
final String privacyGroupId = createResponse.getPrivacyGroupId(); |
||||
|
||||
final List<Base64String> membersEnclaveKeys = |
||||
Arrays.stream(members) |
||||
.map(m -> Base64String.wrap(m.getEnclaveKey())) |
||||
.collect(Collectors.toList()); |
||||
|
||||
for (final PrivacyNode member : members) { |
||||
member.verify(onChainPrivacyGroupExists(privacyGroupId, membersEnclaveKeys)); |
||||
} |
||||
|
||||
final String commitmentHash = |
||||
callGetParticipantsMethodAndReturnCommitmentHash(privacyGroupId, groupCreator, privateFrom); |
||||
final PrivateTransactionReceipt expectedReceipt = |
||||
buildExpectedAddMemberTransactionReceipt( |
||||
privacyGroupId, groupCreator, addresses.toArray(new String[] {})); |
||||
|
||||
for (final PrivacyNode member : members) { |
||||
member.verify( |
||||
privateTransactionVerifier.validPrivateTransactionReceipt( |
||||
commitmentHash, expectedReceipt)); |
||||
} |
||||
|
||||
return privacyGroupId; |
||||
} |
||||
|
||||
protected String callGetParticipantsMethodAndReturnCommitmentHash( |
||||
final String privacyGroupId, final PrivacyNode groupCreator, final String privateFrom) { |
||||
return groupCreator.execute( |
||||
privateContractTransactions.callOnChainPermissioningSmartContract( |
||||
Address.ONCHAIN_PRIVACY_PROXY.toHexString(), |
||||
GET_PARTICIPANTS_METHOD_SIGNATURE.toString(), |
||||
groupCreator.getTransactionSigningKey(), |
||||
POW_CHAIN_ID, |
||||
privateFrom, |
||||
privacyGroupId)); |
||||
} |
||||
|
||||
protected PrivateTransactionReceipt buildExpectedAddMemberTransactionReceipt( |
||||
final String privacyGroupId, final PrivacyNode groupCreator, final String[] members) { |
||||
return buildExpectedAddMemberTransactionReceipt( |
||||
privacyGroupId, groupCreator, groupCreator.getEnclaveKey(), members); |
||||
} |
||||
|
||||
protected PrivateTransactionReceipt buildExpectedAddMemberTransactionReceipt( |
||||
final String privacyGroupId, |
||||
final PrivacyNode groupCreator, |
||||
final String privateFrom, |
||||
final String[] members) { |
||||
final StringBuilder output = new StringBuilder(); |
||||
// hex prefix
|
||||
output.append("0x"); |
||||
// Dynamic array offset
|
||||
output.append("0000000000000000000000000000000000000000000000000000000000000020"); |
||||
// Length of the array (with padded zeros to the left)
|
||||
output.append(Quantity.longToPaddedHex(members.length, 32).substring(2)); |
||||
// Each member enclave key converted from Base64 to bytes
|
||||
for (final String member : members) { |
||||
output.append(Bytes.fromBase64String(member).toUnprefixedHexString()); |
||||
} |
||||
|
||||
return new PrivateTransactionReceipt( |
||||
null, |
||||
groupCreator.getAddress().toHexString(), |
||||
Address.ONCHAIN_PRIVACY_PROXY.toHexString(), |
||||
output.toString(), |
||||
Collections.emptyList(), |
||||
null, |
||||
null, |
||||
privateFrom, |
||||
null, |
||||
privacyGroupId, |
||||
"0x1", |
||||
null); |
||||
} |
||||
|
||||
protected ExpectValidOnChainPrivacyGroupCreated onChainPrivacyGroupExists( |
||||
final String privacyGroupId, final List<Base64String> members) { |
||||
return privateTransactionVerifier.onChainPrivacyGroupExists(privacyGroupId, members); |
||||
} |
||||
|
||||
protected String getContractDeploymentCommitmentHash(final Contract contract) { |
||||
final Optional<TransactionReceipt> transactionReceipt = contract.getTransactionReceipt(); |
||||
assertThat(transactionReceipt).isPresent(); |
||||
final PrivateTransactionReceipt privateTransactionReceipt = |
||||
(PrivateTransactionReceipt) transactionReceipt.get(); |
||||
return privateTransactionReceipt.getcommitmentHash(); |
||||
} |
||||
|
||||
/** |
||||
* This method will check if a privacy group with the specified id and list of members exists. |
||||
* Each one of the members node will be queried to ensure that they all have the same privacy |
||||
* group in their private state. |
||||
* |
||||
* @param privacyGroupId the id of the privacy group |
||||
* @param members the list of member in the privacy group |
||||
*/ |
||||
protected void checkOnChainPrivacyGroupExists( |
||||
final String privacyGroupId, final PrivacyNode... members) { |
||||
final List<Base64String> membersEnclaveKeys = |
||||
Arrays.stream(members) |
||||
.map(PrivacyNode::getEnclaveKey) |
||||
.map(Base64String::wrap) |
||||
.collect(Collectors.toList()); |
||||
|
||||
for (final PrivacyNode member : members) { |
||||
member.verify(onChainPrivacyGroupExists(privacyGroupId, membersEnclaveKeys)); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue