On chain group management api changes (#1288)

- On-chain group management changes: additional API to check whether someone is allowed to update the contract, and remove enclave key as a parameter to contract APIs
- Add check for group membership to onchain and offchain precompiled contracts

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>
pull/1323/head
Stefan Pingel 4 years ago committed by GitHub
parent 1e7f3f8e86
commit 78c458b321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      CHANGELOG.md
  2. 15
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/account/Accounts.java
  3. 13
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToOnChainPrivacyGroupTransaction.java
  4. 2
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindOnChainPrivacyGroupTransaction.java
  5. 8
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockOnChainPrivacyGroupTransaction.java
  6. 20
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java
  7. 13
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RemoveFromOnChainPrivacyGroupTransaction.java
  8. 80
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java
  9. 2
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/RunHelpTest.java
  10. 6
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/Ibft2PrivacyClusterAcceptanceTest.java
  11. 82
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/OnChainPrivacyAcceptanceTest.java
  12. 77
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/PrivacyGroupAcceptanceTest.java
  13. 117
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyGroupTest.java
  14. 89
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyProxyTest.java
  15. 10
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  16. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java
  17. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java
  18. 14
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java
  19. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java
  20. 134
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java
  21. 31
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java
  22. 91
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java
  23. 16
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java
  24. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java
  25. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java
  26. 10
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/group/OnChainGroupManagement.java
  27. 6
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionDataFixture.java
  28. 7
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/PrivateTransactionTestFixture.java
  29. 314
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContractTest.java
  30. 51
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java
  31. 42
      privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/DefaultOnChainPrivacyGroupManagementContract.java
  32. 26
      privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/OnChainPrivacyGroupManagementInterface.java
  33. 128
      privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/OnChainPrivacyGroupManagementProxy.java
  34. 70
      privacy-contracts/src/main/solidity/DefaultOnChainPrivacyGroupManagementContract.sol
  35. 11
      privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementInterface.sol
  36. 43
      privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementProxy.sol

@ -17,6 +17,14 @@
- [Permissioning issues on Kubernetes](KNOWN_ISSUES.md#Kubernetes-permissioning-uses-Service-IPs-rather-than-pod-IPs-which-can-fail)
- [Restarts caused by insufficient memory can cause inconsistent private state](KNOWN_ISSUES.md#Restart-caused-by-insufficient-memory-can-cause-inconsistent-private-state)
### Breaking Changes
When upgrading to 1.5.3, ensure you've taken into account the following breaking changes.
#### Onchain Privacy Group Management
This early access feature was changed in a way that makes onchain privacy groups created with previous versions no longer usable.
## 1.5.2
### Additions and Improvements

@ -21,6 +21,8 @@ public class Accounts {
public static final String GENESIS_ACCOUNT_ONE_PRIVATE_KEY =
"8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63";
public static final String GENESIS_ACCOUNT_TWO_PRIVATE_KEY =
"c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3";
private final EthTransactions eth;
private final Account richBenefactorOne;
@ -31,20 +33,17 @@ public class Accounts {
richBenefactorOne =
Account.fromPrivateKey(eth, "Rich Benefactor One", GENESIS_ACCOUNT_ONE_PRIVATE_KEY);
richBenefactorTwo =
Account.fromPrivateKey(
eth,
"Rich Benefactor Two",
"c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3");
}
public Account getSecondaryBenefactor() {
return richBenefactorTwo;
Account.fromPrivateKey(eth, "Rich Benefactor Two", GENESIS_ACCOUNT_TWO_PRIVATE_KEY);
}
public Account getPrimaryBenefactor() {
return richBenefactorOne;
}
public Account getSecondaryBenefactor() {
return richBenefactorTwo;
}
public Account createAccount(final String accountName) {
return Account.create(eth, accountName);
}

@ -23,18 +23,23 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.crypto.Credentials;
import org.web3j.utils.Base64String;
public class AddToOnChainPrivacyGroupTransaction implements Transaction<String> {
private final Base64String privacyGroupId;
private final PrivacyNode adder;
private final List<String> addresses;
private final Credentials signer;
public AddToOnChainPrivacyGroupTransaction(
final String privacyGroupId, final PrivacyNode adder, final PrivacyNode... nodes) {
final String privacyGroupId,
final PrivacyNode adder,
final Credentials signer,
final PrivacyNode... nodes) {
this.privacyGroupId = Base64String.wrap(privacyGroupId);
this.adder = adder;
this.signer = signer;
this.addresses =
Arrays.stream(nodes)
.map(n -> n.getOrion().getDefaultPublicKey())
@ -44,8 +49,8 @@ public class AddToOnChainPrivacyGroupTransaction implements Transaction<String>
@Override
public String execute(final NodeRequests node) {
try {
return node.privacy().privxAddToPrivacyGroup(privacyGroupId, adder, addresses);
} catch (final IOException | TransactionException e) {
return node.privacy().privxAddToPrivacyGroup(privacyGroupId, adder, signer, addresses);
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

@ -36,7 +36,7 @@ public class FindOnChainPrivacyGroupTransaction
public List<PrivacyRequestFactory.OnChainPrivacyGroup> execute(final NodeRequests node) {
try {
return node.privacy().privxFindOnChainPrivacyGroup(nodes).send().getGroups();
} catch (IOException e) {
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

@ -20,22 +20,26 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.utils.Base64String;
public class LockOnChainPrivacyGroupTransaction implements Transaction<String> {
private final Base64String privacyGroupId;
private final PrivacyNode locker;
private final Credentials signer;
public LockOnChainPrivacyGroupTransaction(final String privacyGroupId, final PrivacyNode locker) {
public LockOnChainPrivacyGroupTransaction(
final String privacyGroupId, final PrivacyNode locker, final Credentials signer) {
this.privacyGroupId = Base64String.wrap(privacyGroupId);
this.locker = locker;
this.signer = signer;
}
@Override
public String execute(final NodeRequests node) {
try {
return node.privacy().privxLockPrivacyGroup(locker, privacyGroupId);
return node.privacy().privxLockPrivacyGroup(locker, privacyGroupId, signer);
} catch (final IOException | TransactionException e) {
throw new RuntimeException(e);
}

@ -29,6 +29,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.Priv
import java.util.List;
import org.web3j.crypto.Credentials;
import org.web3j.tx.Contract;
public class PrivacyTransactions {
@ -49,13 +50,16 @@ public class PrivacyTransactions {
}
public AddToOnChainPrivacyGroupTransaction addToPrivacyGroup(
final String privacyGroupId, final PrivacyNode adder, final PrivacyNode... nodes) {
return new AddToOnChainPrivacyGroupTransaction(privacyGroupId, adder, nodes);
final String privacyGroupId,
final PrivacyNode adder,
final Credentials signer,
final PrivacyNode... nodes) {
return new AddToOnChainPrivacyGroupTransaction(privacyGroupId, adder, signer, nodes);
}
public LockOnChainPrivacyGroupTransaction privxLockPrivacyGroupAndCheck(
final String privacyGroupId, final PrivacyNode locker) {
return new LockOnChainPrivacyGroupTransaction(privacyGroupId, locker);
final String privacyGroupId, final PrivacyNode locker, final Credentials signer) {
return new LockOnChainPrivacyGroupTransaction(privacyGroupId, locker, signer);
}
public FindPrivacyGroupTransaction findPrivacyGroup(final List<String> nodes) {
@ -87,8 +91,12 @@ public class PrivacyTransactions {
}
public RemoveFromOnChainPrivacyGroupTransaction removeFromPrivacyGroup(
final String privacyGroupId, final PrivacyNode remover, final PrivacyNode nodeToRemove) {
return new RemoveFromOnChainPrivacyGroupTransaction(privacyGroupId, remover, nodeToRemove);
final String privacyGroupId,
final PrivacyNode remover,
final Credentials signer,
final PrivacyNode nodeToRemove) {
return new RemoveFromOnChainPrivacyGroupTransaction(
privacyGroupId, remover, signer, nodeToRemove);
}
public EeaSendRawTransactionTransaction sendRawTransaction(final String transaction) {

@ -20,26 +20,31 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.crypto.Credentials;
import org.web3j.utils.Base64String;
public class RemoveFromOnChainPrivacyGroupTransaction implements Transaction<String> {
private final Base64String privacyGroupId;
private final PrivacyNode remover;
private final String toRemove;
private final Credentials signer;
public RemoveFromOnChainPrivacyGroupTransaction(
final String privacyGroupId, final PrivacyNode remover, final PrivacyNode toRemove) {
final String privacyGroupId,
final PrivacyNode remover,
final Credentials signer,
final PrivacyNode toRemove) {
this.privacyGroupId = Base64String.wrap(privacyGroupId);
this.remover = remover;
this.signer = signer;
this.toRemove = toRemove.getOrion().getDefaultPublicKey();
}
@Override
public String execute(final NodeRequests node) {
try {
return node.privacy().privxRemoveFromPrivacyGroup(privacyGroupId, remover, toRemove);
} catch (IOException | TransactionException e) {
return node.privacy().privxRemoveFromPrivacyGroup(privacyGroupId, remover, signer, toRemove);
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

@ -49,6 +49,7 @@ import org.web3j.protocol.core.Response;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthFilter;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.EthUninstallFilter;
import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder;
import org.web3j.protocol.eea.crypto.RawPrivateTransaction;
@ -127,18 +128,20 @@ public class PrivacyRequestFactory {
}
public String privxAddToPrivacyGroup(
final Base64String privacyGroupId, final PrivacyNode adder, final List<String> addresses)
throws IOException, TransactionException {
final Base64String privacyGroupId,
final PrivacyNode adder,
final Credentials signer,
final List<String> addresses)
throws IOException {
final BigInteger nonce =
besuClient
.privGetTransactionCount(adder.getAddress().toHexString(), privacyGroupId)
.privGetTransactionCount(signer.getAddress(), privacyGroupId)
.send()
.getTransactionCount();
final Bytes payload =
encodeAddToGroupFunctionCall(
Bytes.fromBase64String(adder.getEnclaveKey()),
addresses.stream().map(Bytes::fromBase64String).collect(Collectors.toList()));
final RawPrivateTransaction privateTransaction =
@ -154,26 +157,25 @@ public class PrivacyRequestFactory {
return besuClient
.eeaSendRawTransaction(
Numeric.toHexString(
PrivateTransactionEncoder.signMessage(
privateTransaction, Credentials.create(adder.getTransactionSigningKey()))))
Numeric.toHexString(PrivateTransactionEncoder.signMessage(privateTransaction, signer)))
.send()
.getTransactionHash();
}
public String privxRemoveFromPrivacyGroup(
final Base64String privacyGroupId, final PrivacyNode remover, final String toRemove)
throws IOException, TransactionException {
final Base64String privacyGroupId,
final PrivacyNode remover,
final Credentials signer,
final String toRemove)
throws IOException {
final BigInteger nonce =
besuClient
.privGetTransactionCount(remover.getAddress().toHexString(), privacyGroupId)
.privGetTransactionCount(signer.getAddress(), privacyGroupId)
.send()
.getTransactionCount();
final Bytes payload =
encodeRemoveFromGroupFunctionCall(
Bytes.fromBase64String(remover.getEnclaveKey()), Bytes.fromBase64String(toRemove));
final Bytes payload = encodeRemoveFromGroupFunctionCall(Bytes.fromBase64String(toRemove));
final RawPrivateTransaction privateTransaction =
RawPrivateTransaction.createTransaction(
@ -188,23 +190,21 @@ public class PrivacyRequestFactory {
return besuClient
.eeaSendRawTransaction(
Numeric.toHexString(
PrivateTransactionEncoder.signMessage(
privateTransaction, Credentials.create(remover.getTransactionSigningKey()))))
Numeric.toHexString(PrivateTransactionEncoder.signMessage(privateTransaction, signer)))
.send()
.getTransactionHash();
}
private Bytes encodeRemoveFromGroupFunctionCall(final Bytes remover, final Bytes toRemove) {
return Bytes.concatenate(
OnChainGroupManagement.REMOVE_PARTICIPANT_METHOD_SIGNATURE, remover, toRemove);
private Bytes encodeRemoveFromGroupFunctionCall(final Bytes toRemove) {
return Bytes.concatenate(OnChainGroupManagement.REMOVE_PARTICIPANT_METHOD_SIGNATURE, toRemove);
}
public String privxLockPrivacyGroup(final PrivacyNode locker, final Base64String privacyGroupId)
public String privxLockPrivacyGroup(
final PrivacyNode locker, final Base64String privacyGroupId, final Credentials signer)
throws IOException, TransactionException {
final BigInteger nonce =
besuClient
.privGetTransactionCount(locker.getAddress().toHexString(), privacyGroupId)
.privGetTransactionCount(signer.getAddress(), privacyGroupId)
.send()
.getTransactionCount();
@ -223,8 +223,7 @@ public class PrivacyRequestFactory {
besuClient
.eeaSendRawTransaction(
Numeric.toHexString(
PrivateTransactionEncoder.signMessage(
privateTransaction, Credentials.create(locker.getTransactionSigningKey()))))
PrivateTransactionEncoder.signMessage(privateTransaction, signer)))
.send()
.getTransactionHash();
@ -244,22 +243,13 @@ public class PrivacyRequestFactory {
secureRandom.nextBytes(bytes);
final Bytes privacyGroupId = Bytes.wrap(bytes);
final BigInteger nonce =
besuClient
.privGetTransactionCount(
creator.getAddress().toHexString(),
Base64String.wrap(privacyGroupId.toArrayUnsafe()))
.send()
.getTransactionCount();
final Bytes payload =
encodeAddToGroupFunctionCall(
Bytes.fromBase64String(creator.getEnclaveKey()),
addresses.stream().map(Bytes::fromBase64String).collect(Collectors.toList()));
final RawPrivateTransaction privateTransaction =
RawPrivateTransaction.createTransaction(
nonce,
BigInteger.ZERO,
BigInteger.valueOf(1000),
BigInteger.valueOf(3000000),
Address.ONCHAIN_PRIVACY_PROXY.toHexString(),
@ -268,15 +258,12 @@ public class PrivacyRequestFactory {
Base64String.wrap(privacyGroupId.toArrayUnsafe()),
org.web3j.utils.Restriction.RESTRICTED);
final String transactionHash =
besuClient
.eeaSendRawTransaction(
Numeric.toHexString(
PrivateTransactionEncoder.signMessage(
privateTransaction,
Credentials.create(creator.getTransactionSigningKey()))))
.send()
.getTransactionHash();
final Request<?, EthSendTransaction> ethSendTransactionRequest =
besuClient.eeaSendRawTransaction(
Numeric.toHexString(
PrivateTransactionEncoder.signMessage(
privateTransaction, Credentials.create(creator.getTransactionSigningKey()))));
final String transactionHash = ethSendTransactionRequest.send().getTransactionHash();
return new PrivxCreatePrivacyGroupResponse(privacyGroupId.toBase64String(), transactionHash);
}
@ -531,16 +518,13 @@ public class PrivacyRequestFactory {
}
}
private Bytes encodeAddToGroupFunctionCall(
final Bytes privateFrom, final List<Bytes> participants) {
private Bytes encodeAddToGroupFunctionCall(final List<Bytes> participants) {
return Bytes.concatenate(
OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE,
privateFrom,
encodeList(participants));
OnChainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE, encodeList(participants));
}
private Bytes encodeList(final List<Bytes> participants) {
final Bytes dynamicParameterOffset = encodeLong(64);
final Bytes dynamicParameterOffset = encodeLong(32);
final Bytes length = encodeLong(participants.size());
return Bytes.concatenate(
dynamicParameterOffset,

@ -26,7 +26,7 @@ public class RunHelpTest extends AcceptanceTestBase {
@Test
public void testShowsHelpAndExits() throws IOException {
BesuNode node = besu.runCommand("--help");
final BesuNode node = besu.runCommand("--help");
cluster.runNodeStart(node);
WaitUtils.waitFor(5000, () -> node.verify(exitedSuccessfully));
}

@ -144,16 +144,16 @@ public class Ibft2PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase
bob.getEnclaveKey()));
// alice gets receipt from charlie's interaction
final PrivateTransactionReceipt firstExpectedReceipt =
final PrivateTransactionReceipt aliceReceipt =
alice.execute(privacyTransactions.getPrivateTransactionReceipt(firstTransactionHash));
// verify bob and charlie have access to the same receipt
bob.verify(
privateTransactionVerifier.validPrivateTransactionReceipt(
firstTransactionHash, firstExpectedReceipt));
firstTransactionHash, aliceReceipt));
charlie.verify(
privateTransactionVerifier.validPrivateTransactionReceipt(
firstTransactionHash, firstExpectedReceipt));
firstTransactionHash, aliceReceipt));
// alice deploys second contract
final String secondDeployedAddress = "0xebf56429e6500e84442467292183d4d621359838";

@ -39,6 +39,7 @@ import com.google.common.collect.Lists;
import org.apache.tuweni.bytes.Bytes;
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.protocol.core.methods.response.Log;
@ -78,8 +79,8 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
@Test
public void nodeCanCreatePrivacyGroup() {
final String privacyGroupId = createOnChainPrivacyGroup(alice, bob);
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob);
final String privacyGroupId = createOnChainPrivacyGroup(alice);
checkOnChainPrivacyGroupExists(privacyGroupId, alice);
}
@Test
@ -100,8 +101,9 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(commitmentHash));
lockPrivacyGroup(privacyGroupId, alice);
addMembersToPrivacyGroup(privacyGroupId, alice, charlie);
final Credentials aliceCredentials = Credentials.create(alice.getTransactionSigningKey());
lockPrivacyGroup(privacyGroupId, alice, aliceCredentials);
addMembersToPrivacyGroup(privacyGroupId, alice, aliceCredentials, charlie);
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob, charlie);
charlie.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(commitmentHash));
@ -111,7 +113,9 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
public void removedMemberCannotSendTransactionToGroup() {
final String privacyGroupId = createOnChainPrivacyGroup(alice, bob);
final String removeHash = removeFromPrivacyGroup(privacyGroupId, alice, bob);
final String removeHash =
removeFromPrivacyGroup(
privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob);
bob.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(removeHash));
@ -126,8 +130,9 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
checkOnChainPrivacyGroupExists(privacyGroupId, alice);
lockPrivacyGroup(privacyGroupId, alice);
addMembersToPrivacyGroup(privacyGroupId, alice, bob);
lockPrivacyGroup(privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()));
addMembersToPrivacyGroup(
privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob);
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob);
@ -142,7 +147,8 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
eventEmitter,
valueSetWhileBobWasMember);
removeFromPrivacyGroup(privacyGroupId, alice, bob);
removeFromPrivacyGroup(
privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob);
checkOnChainPrivacyGroupExists(privacyGroupId, alice);
@ -159,8 +165,9 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
eventEmitter,
valueSetWhileBobWasMember); // bob did not get the last transaction
lockPrivacyGroup(privacyGroupId, alice);
addMembersToPrivacyGroup(privacyGroupId, alice, bob);
lockPrivacyGroup(privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()));
addMembersToPrivacyGroup(
privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob);
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob);
@ -187,11 +194,11 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
final List<PrivacyNode> nodes,
final String privacyGroupId,
final EventEmitter eventEmitter,
final int valueSetWhileBobWasMember) {
final int value) {
final PrivateTransactionReceipt receiptWhileBobMember =
setEventEmitterValue(nodes, privacyGroupId, eventEmitter, valueSetWhileBobWasMember);
setEventEmitterValue(nodes, privacyGroupId, eventEmitter, value);
checkEmitterValue(nodes, privacyGroupId, eventEmitter, valueSetWhileBobWasMember);
checkEmitterValue(nodes, privacyGroupId, eventEmitter, value);
return receiptWhileBobMember;
}
@ -222,7 +229,7 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
final List<PrivacyNode> nodes,
final String privacyGroupId,
final EventEmitter eventEmitter,
final int valueWhileBobMember) {
final int expectedValue) {
for (final PrivacyNode node : nodes) {
final EthCall response =
node.execute(
@ -230,14 +237,14 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall()));
assertThat(new BigInteger(response.getValue().substring(2), 16))
.isEqualByComparingTo(BigInteger.valueOf(valueWhileBobMember));
.isEqualByComparingTo(BigInteger.valueOf(expectedValue));
}
}
@Test
public void bobCanAddCharlieAfterBeingAddedByAlice() {
final String privacyGroupId = createOnChainPrivacyGroup(alice);
checkOnChainPrivacyGroupExists(privacyGroupId, alice);
final EventEmitter eventEmitter =
alice.execute(
privateContractTransactions.createSmartContractWithPrivacyGroupId(
@ -252,14 +259,19 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
eventEmitter.getContractAddress(), alice.getAddress().toString())
.verify(eventEmitter);
final Credentials aliceCredentials = Credentials.create(alice.getTransactionSigningKey());
final String aliceLockHash =
alice.execute(privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, alice));
alice.execute(
privacyTransactions.privxLockPrivacyGroupAndCheck(
privacyGroupId, alice, aliceCredentials));
alice.execute(privacyTransactions.addToPrivacyGroup(privacyGroupId, alice, bob));
alice.execute(
privacyTransactions.addToPrivacyGroup(privacyGroupId, alice, aliceCredentials, bob));
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob);
bob.execute(privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, bob));
bob.execute(
privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, bob, aliceCredentials));
alice.execute(minerTransactions.minerStop());
@ -278,7 +290,8 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
alice.getEnclaveKey(),
privacyGroupId));
final String bobAddHash = addMembersToPrivacyGroup(privacyGroupId, bob, charlie);
final String bobAddHash =
addMembersToPrivacyGroup(privacyGroupId, bob, aliceCredentials, charlie);
alice
.getBesu()
@ -392,20 +405,20 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
privateTransactionVerifier.validPrivateTransactionReceipt(
aliceStoreHash, expectedStoreReceipt));
removeFromPrivacyGroup(privacyGroupId, bob, charlie);
removeFromPrivacyGroup(privacyGroupId, bob, aliceCredentials, charlie);
checkOnChainPrivacyGroupExists(privacyGroupId, alice, bob);
}
@Test
public void addMembersToTwoGroupsInTheSameBlock() throws InterruptedException {
public void addMembersToTwoGroupsInTheSameBlock() {
final String privacyGroupId1 = createOnChainPrivacyGroup(alice);
final String privacyGroupId2 = createOnChainPrivacyGroup(bob);
checkOnChainPrivacyGroupExists(privacyGroupId1, alice);
checkOnChainPrivacyGroupExists(privacyGroupId2, bob);
lockPrivacyGroup(privacyGroupId1, alice);
lockPrivacyGroup(privacyGroupId2, bob);
lockPrivacyGroup(privacyGroupId1, alice, Credentials.create(alice.getTransactionSigningKey()));
lockPrivacyGroup(privacyGroupId2, bob, Credentials.create(bob.getTransactionSigningKey()));
final BigInteger pendingTransactionFilterId =
alice.execute(ethTransactions.newPendingTransactionsFilter());
@ -413,8 +426,12 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
alice.execute(minerTransactions.minerStop());
alice.getBesu().verify(ethConditions.miningStatus(false));
final String aliceAddHash = addMembersToPrivacyGroup(privacyGroupId1, alice, charlie);
final String bobAddHash = addMembersToPrivacyGroup(privacyGroupId2, bob, alice);
final String aliceAddHash =
addMembersToPrivacyGroup(
privacyGroupId1, alice, Credentials.create(alice.getTransactionSigningKey()), charlie);
final String bobAddHash =
addMembersToPrivacyGroup(
privacyGroupId2, bob, Credentials.create(bob.getTransactionSigningKey()), alice);
alice
.getBesu()
@ -457,6 +474,7 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
* @return the id of the privacy group
*/
private String createOnChainPrivacyGroup(final PrivacyNode... members) {
final PrivacyNode groupCreator = members[0];
final CreateOnChainPrivacyGroupTransaction createTx =
@ -485,18 +503,21 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
private String addMembersToPrivacyGroup(
final String privacyGroupId,
final PrivacyNode nodeAddingMember,
final Credentials signer,
final PrivacyNode... newMembers) {
return nodeAddingMember.execute(
privacyTransactions.addToPrivacyGroup(privacyGroupId, nodeAddingMember, newMembers));
privacyTransactions.addToPrivacyGroup(
privacyGroupId, nodeAddingMember, signer, newMembers));
}
private String removeFromPrivacyGroup(
final String privacyGroupId,
final PrivacyNode nodeRemovingMember,
final Credentials signer,
final PrivacyNode memberBeingRemoved) {
return nodeRemovingMember.execute(
privacyTransactions.removeFromPrivacyGroup(
privacyGroupId, nodeRemovingMember, memberBeingRemoved));
privacyGroupId, nodeRemovingMember, signer, memberBeingRemoved));
}
private String calculateAddParticipantPMTHash(
@ -564,9 +585,10 @@ public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase {
*
* @return the hash of the lock privacy group transaction
*/
private String lockPrivacyGroup(final String privacyGroupId, final PrivacyNode member) {
private String lockPrivacyGroup(
final String privacyGroupId, final PrivacyNode member, final Credentials signer) {
return member.execute(
privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, member));
privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, member, signer));
}
private ExpectValidOnChainPrivacyGroupCreated onChainPrivacyGroupExists(

@ -18,10 +18,14 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode;
import org.hyperledger.besu.tests.web3j.generated.EventEmitter;
import java.math.BigInteger;
import org.junit.Before;
import org.junit.Test;
import org.web3j.protocol.besu.response.privacy.PrivacyGroup;
import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt;
import org.web3j.utils.Base64String;
public class PrivacyGroupAcceptanceTest extends PrivacyAcceptanceTestBase {
@ -124,4 +128,77 @@ public class PrivacyGroupAcceptanceTest extends PrivacyAcceptanceTestBase {
bob.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected));
}
@Test
public void canInteractWithMultiplePrivacyGroups() {
final String privacyGroupIdABC =
alice.execute(privacyTransactions.createPrivacyGroup(null, null, alice, bob, charlie));
final EventEmitter firstEventEmitter =
alice.execute(
privateContractTransactions.createSmartContractWithPrivacyGroupId(
EventEmitter.class,
alice.getTransactionSigningKey(),
POW_CHAIN_ID,
alice.getEnclaveKey(),
privacyGroupIdABC));
// charlie interacts with contract
final String firstTransactionHash =
charlie.execute(
privateContractTransactions.callSmartContractWithPrivacyGroupId(
firstEventEmitter.getContractAddress(),
firstEventEmitter.store(BigInteger.ONE).encodeFunctionCall(),
charlie.getTransactionSigningKey(),
POW_CHAIN_ID,
charlie.getEnclaveKey(),
privacyGroupIdABC));
// alice gets receipt from charlie's interaction
final PrivateTransactionReceipt firstExpectedReceipt =
alice.execute(privacyTransactions.getPrivateTransactionReceipt(firstTransactionHash));
// verify bob and charlie have access to the same receipt
bob.verify(
privateTransactionVerifier.validPrivateTransactionReceipt(
firstTransactionHash, firstExpectedReceipt));
charlie.verify(
privateTransactionVerifier.validPrivateTransactionReceipt(
firstTransactionHash, firstExpectedReceipt));
// alice deploys second contract
final String privacyGroupIdAB =
alice.execute(privacyTransactions.createPrivacyGroup(null, null, alice, bob));
final EventEmitter secondEventEmitter =
alice.execute(
privateContractTransactions.createSmartContractWithPrivacyGroupId(
EventEmitter.class,
alice.getTransactionSigningKey(),
POW_CHAIN_ID,
alice.getEnclaveKey(),
privacyGroupIdAB));
// bob interacts with contract
final String secondTransactionHash =
bob.execute(
privateContractTransactions.callSmartContractWithPrivacyGroupId(
secondEventEmitter.getContractAddress(),
secondEventEmitter.store(BigInteger.ONE).encodeFunctionCall(),
bob.getTransactionSigningKey(),
POW_CHAIN_ID,
bob.getEnclaveKey(),
privacyGroupIdAB));
// alice gets receipt from bob's interaction
final PrivateTransactionReceipt secondExpectedReceipt =
alice.execute(privacyTransactions.getPrivateTransactionReceipt(secondTransactionHash));
bob.verify(
privateTransactionVerifier.validPrivateTransactionReceipt(
secondTransactionHash, secondExpectedReceipt));
// charlie cannot see the receipt
charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(secondTransactionHash));
}
}

@ -20,11 +20,14 @@ import org.hyperledger.besu.privacy.contracts.generated.DefaultOnChainPrivacyGro
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.utils.Base64String;
@ -37,14 +40,13 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
Base64String.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=");
private final Base64String thirdParticipant =
Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=");
private DefaultOnChainPrivacyGroupManagementContract privacyGroup;
private DefaultOnChainPrivacyGroupManagementContract defaultPrivacyGroupManagementContract;
private static final String RAW_FIRST_PARTICIPANT =
"0x0b0235be035695b4cc4b0941e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486a";
private static final String RAW_FIRST_PARTICIPANT = "0x5aa68ac0";
private static final String RAW_ADD_PARTICIPANT =
"0xf744b089035695b4cc4b0941e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000012a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b";
"0xb4926e25000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000012a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b";
private static final String RAW_REMOVE_PARTICIPANT =
"0x61544c91035695b4cc4b0941e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486a2a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b";
"0xfd0177972a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b";
private static final String RAW_LOCK = "0xf83d08ba";
private static final String RAW_UNLOCK = "0xa69df4b5";
private static final String RAW_CAN_EXECUTE = "0x78b90337";
@ -56,7 +58,7 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
public void setUp() throws Exception {
minerNode = besu.createMinerNode("node");
cluster.start(minerNode);
privacyGroup =
defaultPrivacyGroupManagementContract =
minerNode.execute(
contractTransactions.createSmartContract(
DefaultOnChainPrivacyGroupManagementContract.class));
@ -65,36 +67,43 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
@Test
public void rlp() throws Exception {
final String contractAddress = "0x42699a7612a82f1d9c36148af9c77354759b210b";
assertThat(privacyGroup.isValid()).isEqualTo(true);
contractVerifier.validTransactionReceipt(contractAddress).verify(privacyGroup);
assertThat(defaultPrivacyGroupManagementContract.isValid()).isEqualTo(true);
contractVerifier
.validTransactionReceipt(contractAddress)
.verify(defaultPrivacyGroupManagementContract);
// 0x0b0235be
assertThat(RAW_FIRST_PARTICIPANT)
.isEqualTo(privacyGroup.getParticipants(firstParticipant.raw()).encodeFunctionCall());
.isEqualTo(defaultPrivacyGroupManagementContract.getParticipants().encodeFunctionCall());
// 0xf744b089
assertThat(RAW_ADD_PARTICIPANT)
.isEqualTo(
privacyGroup
.addParticipants(
firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(secondParticipant.raw()))
.encodeFunctionCall());
// 0xf744b089
assertThat(RAW_REMOVE_PARTICIPANT)
.isEqualTo(
privacyGroup
.removeParticipant(firstParticipant.raw(), secondParticipant.raw())
defaultPrivacyGroupManagementContract
.removeParticipant(secondParticipant.raw())
.encodeFunctionCall());
assertThat(RAW_LOCK).isEqualTo(privacyGroup.lock().encodeFunctionCall());
assertThat(RAW_UNLOCK).isEqualTo(privacyGroup.unlock().encodeFunctionCall());
assertThat(RAW_CAN_EXECUTE).isEqualTo(privacyGroup.canExecute().encodeFunctionCall());
assertThat(RAW_GET_VERSION).isEqualTo(privacyGroup.getVersion().encodeFunctionCall());
assertThat(RAW_LOCK)
.isEqualTo(defaultPrivacyGroupManagementContract.lock().encodeFunctionCall());
assertThat(RAW_UNLOCK)
.isEqualTo(defaultPrivacyGroupManagementContract.unlock().encodeFunctionCall());
assertThat(RAW_CAN_EXECUTE)
.isEqualTo(defaultPrivacyGroupManagementContract.canExecute().encodeFunctionCall());
assertThat(RAW_GET_VERSION)
.isEqualTo(defaultPrivacyGroupManagementContract.getVersion().encodeFunctionCall());
}
@Test
public void canInitiallyAddParticipants() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
.send();
final List<byte[]> participants = privacyGroup.getParticipants(firstParticipant.raw()).send();
final RemoteFunctionCall<TransactionReceipt> transactionReceiptRemoteFunctionCall =
defaultPrivacyGroupManagementContract.addParticipants(
Arrays.asList(firstParticipant.raw(), secondParticipant.raw()));
transactionReceiptRemoteFunctionCall.send();
final List<byte[]> participants =
defaultPrivacyGroupManagementContract.getParticipants().send();
assertThat(participants.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participants.get(1));
@ -102,51 +111,50 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
@Test
public void canRemoveParticipant() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw()))
.send();
final List<byte[]> participants = privacyGroup.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants =
defaultPrivacyGroupManagementContract.getParticipants().send();
assertThat(participants.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participants.get(1));
privacyGroup.removeParticipant(firstParticipant.raw(), secondParticipant.raw()).send();
defaultPrivacyGroupManagementContract.removeParticipant(secondParticipant.raw()).send();
final List<byte[]> participantsAfterRemove =
privacyGroup.getParticipants(firstParticipant.raw()).send();
defaultPrivacyGroupManagementContract.getParticipants().send();
assertThat(participantsAfterRemove.size()).isEqualTo(1);
assertThat(firstParticipant.raw()).isEqualTo(participantsAfterRemove.get(0));
}
@Test(expected = TransactionException.class)
public void cannotAddToContractWhenNotLocked() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(thirdParticipant.raw()))
.send();
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(secondParticipant.raw()))
.send();
}
@Test
public void ensureContractIsNotLockedAfterDeploy() throws Exception {
privacyGroup.unlock().send();
assertThat(privacyGroup.canExecute().send()).isTrue();
public void ensureContractIsLockedAfterDeploy() throws Exception {
assertThat(defaultPrivacyGroupManagementContract.canExecute().send()).isFalse();
}
@Test
public void ensurePrivacyGroupVersionIsAlwaysDifferent() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(secondParticipant.raw()))
.send();
final byte[] version1 = privacyGroup.getVersion().send();
privacyGroup.lock().send();
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw()))
final byte[] version1 = defaultPrivacyGroupManagementContract.getVersion().send();
defaultPrivacyGroupManagementContract.lock().send();
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(thirdParticipant.raw()))
.send();
final byte[] version2 = privacyGroup.getVersion().send();
privacyGroup.lock().send();
privacyGroup.removeParticipant(firstParticipant.raw(), secondParticipant.raw()).send();
final byte[] version3 = privacyGroup.getVersion().send();
final byte[] version2 = defaultPrivacyGroupManagementContract.getVersion().send();
defaultPrivacyGroupManagementContract.removeParticipant(secondParticipant.raw()).send();
final byte[] version3 = defaultPrivacyGroupManagementContract.getVersion().send();
assertThat(version1).isNotEqualTo(version2);
assertThat(version1).isNotEqualTo(version3);
@ -155,15 +163,16 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
@Test
public void canAddTwiceToContractWhenCallLock() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Arrays.asList(firstParticipant.raw(), thirdParticipant.raw()))
.send();
privacyGroup.lock().send();
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
defaultPrivacyGroupManagementContract.lock().send();
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(secondParticipant.raw()))
.send();
final List<byte[]> participants = privacyGroup.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants =
defaultPrivacyGroupManagementContract.getParticipants().send();
assertThat(participants.size()).isEqualTo(3);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1));
@ -172,10 +181,10 @@ public class PrivacyGroupTest extends AcceptanceTestBase {
@Test(expected = TransactionException.class)
public void cannotLockTwice() throws Exception {
privacyGroup
.addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw()))
defaultPrivacyGroupManagementContract
.addParticipants(Collections.singletonList(thirdParticipant.raw()))
.send();
privacyGroup.lock().send();
privacyGroup.lock().send();
defaultPrivacyGroupManagementContract.lock().send();
defaultPrivacyGroupManagementContract.lock().send();
}
}

@ -15,18 +15,26 @@
package org.hyperledger.besu.tests.web3j.privacy.contracts;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.privacy.contracts.generated.DefaultOnChainPrivacyGroupManagementContract;
import org.hyperledger.besu.privacy.contracts.generated.OnChainPrivacyGroupManagementProxy;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.utils.Base64String;
@SuppressWarnings("unchecked")
@ -40,25 +48,26 @@ public class PrivacyProxyTest extends AcceptanceTestBase {
Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=");
private OnChainPrivacyGroupManagementProxy onChainPrivacyGroupManagementProxy;
private static final String RAW_FIRST_PARTICIPANT =
"0x0b0235bef772b2ee55f016431cefe724a05814324bb96e9afdb73e338665a693d4653678";
private static final String RAW_GET_PARTICIPANTS = "0x5aa68ac0";
private static final String RAW_ADD_PARTICIPANT =
"0xf744b089f772b2ee55f016431cefe724a05814324bb96e9afdb73e338665a693d465367800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000";
"0xb4926e2500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000";
private BesuNode minerNode;
private DefaultOnChainPrivacyGroupManagementContract defaultOnChainPrivacyGroupManagementContract;
@Before
public void setUp() throws Exception {
minerNode = besu.createMinerNode("node");
cluster.start(minerNode);
DefaultOnChainPrivacyGroupManagementContract privacyGroup =
defaultOnChainPrivacyGroupManagementContract =
minerNode.execute(
contractTransactions.createSmartContract(
DefaultOnChainPrivacyGroupManagementContract.class));
onChainPrivacyGroupManagementProxy =
minerNode.execute(
contractTransactions.createSmartContract(
OnChainPrivacyGroupManagementProxy.class, privacyGroup.getContractAddress()));
OnChainPrivacyGroupManagementProxy.class,
defaultOnChainPrivacyGroupManagementContract.getContractAddress()));
}
@Test
@ -67,47 +76,39 @@ public class PrivacyProxyTest extends AcceptanceTestBase {
contractVerifier
.validTransactionReceipt(onChainPrivacyGroupManagementProxy.getContractAddress())
.verify(onChainPrivacyGroupManagementProxy);
// 0x0b0235be
assertThat(RAW_FIRST_PARTICIPANT)
.isEqualTo(
onChainPrivacyGroupManagementProxy
.getParticipants(firstParticipant.raw())
.encodeFunctionCall());
// 0xf744b089
assertThat(RAW_GET_PARTICIPANTS)
.isEqualTo(onChainPrivacyGroupManagementProxy.getParticipants().encodeFunctionCall());
assertThat(RAW_ADD_PARTICIPANT)
.isEqualTo(
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.emptyList())
.addParticipants(Collections.emptyList())
.encodeFunctionCall());
}
@Ignore("return 0x which causes web3j to throw exception instead of return empty list")
@Test
public void deploysWithNoParticipant() throws Exception {
final List<byte[]> participants =
onChainPrivacyGroupManagementProxy.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants = onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participants.size()).isEqualTo(0);
}
@Test
public void canAddParticipants() throws Exception {
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
.addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw()))
.send();
final List<byte[]> participants =
onChainPrivacyGroupManagementProxy.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants = onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participants.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participants.get(1));
}
@Test
public void canUpgrade() throws Exception {
public void nonOwnerCannotUpgrade() throws Exception {
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
.addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw()))
.send();
final List<byte[]> participants =
onChainPrivacyGroupManagementProxy.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants = onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participants.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participants.get(1));
@ -117,12 +118,43 @@ public class PrivacyProxyTest extends AcceptanceTestBase {
contractTransactions.createSmartContract(
DefaultOnChainPrivacyGroupManagementContract.class));
onChainPrivacyGroupManagementProxy.upgradeTo(upgradedContract.getContractAddress()).send();
final HttpService httpService =
new HttpService(
"http://" + minerNode.getHostName() + ":" + minerNode.getJsonRpcSocketPort().get());
final Web3j web3j = Web3j.build(httpService);
// load the proxy contract, use it with another signer
final OnChainPrivacyGroupManagementProxy proxyContractAccount2 =
OnChainPrivacyGroupManagementProxy.load(
onChainPrivacyGroupManagementProxy.getContractAddress(),
web3j,
Credentials.create(Accounts.GENESIS_ACCOUNT_TWO_PRIVATE_KEY),
new DefaultGasProvider());
// contract is the proxy contract and uses genesis account 2. It should not be able to upgrade
// the contract, because it is not the owner of "upgradedContract"
assertThatThrownBy(
() -> proxyContractAccount2.upgradeTo(upgradedContract.getContractAddress()).send())
.isInstanceOf(TransactionException.class);
}
@Test
public void ownerCanUpgrade() throws Exception {
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
.addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw()))
.send();
final List<byte[]> participants = onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participants.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participants.get(1));
final DefaultOnChainPrivacyGroupManagementContract upgradedContract =
minerNode.execute(
contractTransactions.createSmartContract(
DefaultOnChainPrivacyGroupManagementContract.class));
onChainPrivacyGroupManagementProxy.upgradeTo(upgradedContract.getContractAddress()).send();
final List<byte[]> participantsAfterUpgrade =
onChainPrivacyGroupManagementProxy.getParticipants(firstParticipant.raw()).send();
onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participantsAfterUpgrade.size()).isEqualTo(2);
assertThat(firstParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(0));
assertThat(secondParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(1));
@ -131,14 +163,13 @@ public class PrivacyProxyTest extends AcceptanceTestBase {
@Test
public void canAddTwiceToContractWhenCallLock() throws Exception {
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw()))
.addParticipants(Arrays.asList(firstParticipant.raw(), thirdParticipant.raw()))
.send();
onChainPrivacyGroupManagementProxy.lock().send();
onChainPrivacyGroupManagementProxy
.addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw()))
.addParticipants(Collections.singletonList(secondParticipant.raw()))
.send();
final List<byte[]> participants =
onChainPrivacyGroupManagementProxy.getParticipants(firstParticipant.raw()).send();
final List<byte[]> participants = onChainPrivacyGroupManagementProxy.getParticipants().send();
assertThat(participants.size()).isEqualTo(3);
assertThat(firstParticipant.raw()).isEqualTo(participants.get(0));
assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1));

@ -16,6 +16,9 @@ package org.hyperledger.besu;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.controller.BesuController;
@ -24,6 +27,7 @@ import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.enclave.types.PrivacyGroup;
import org.hyperledger.besu.enclave.types.SendResponse;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
@ -42,6 +46,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.privacy.DefaultPrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.Restriction;
@ -69,6 +74,7 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import io.vertx.core.Vertx;
@ -130,6 +136,7 @@ public class PrivacyReorgTest {
private OrionTestHarness enclave;
private PrivateStateRootResolver privateStateRootResolver;
private PrivacyParameters privacyParameters;
private DefaultPrivacyController privacyController;
@Before
public void setUp() throws IOException {
@ -153,6 +160,9 @@ public class PrivacyReorgTest {
.setEnclaveFactory(new EnclaveFactory(Vertx.vertx()))
.build();
privacyParameters.setEnclavePublicKey(ENCLAVE_PUBLIC_KEY.toBase64String());
privacyController = mock(DefaultPrivacyController.class);
when(privacyController.retrieveOffChainPrivacyGroup(any(), any()))
.thenReturn(Optional.of(new PrivacyGroup()));
privateStateRootResolver =
new PrivateStateRootResolver(privacyParameters.getPrivateStateStorage());

@ -93,10 +93,9 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_ID_NOT_AVAILABLE);
}
maybePrivacyGroup =
privacyController.retrieveOnChainPrivacyGroup(
maybePrivacyGroupId.get(), enclavePublicKey);
if (maybePrivacyGroup.isEmpty()
&& !privacyController.isGroupAdditionTransaction(privateTransaction)) {
privacyController.retrieveOnChainPrivacyGroupWithToBeAddedMembers(
maybePrivacyGroupId.get(), enclavePublicKey, privateTransaction);
if (maybePrivacyGroup.isEmpty()) {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST);
}
} else { // !onchainPirvacyGroupEnabled

@ -88,10 +88,9 @@ public class PrivDistributeRawTransaction implements JsonRpcMethod {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_ID_NOT_AVAILABLE);
}
maybePrivacyGroup =
privacyController.retrieveOnChainPrivacyGroup(
maybePrivacyGroupId.get(), enclavePublicKey);
if (maybePrivacyGroup.isEmpty()
&& !privacyController.isGroupAdditionTransaction(privateTransaction)) {
privacyController.retrieveOnChainPrivacyGroupWithToBeAddedMembers(
maybePrivacyGroupId.get(), enclavePublicKey, privateTransaction);
if (maybePrivacyGroup.isEmpty()) {
return new JsonRpcErrorResponse(id, JsonRpcError.ONCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST);
}
} else { // !onchainPirvacyGroupEnabled

@ -50,7 +50,7 @@ import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@ -255,10 +255,12 @@ public class EeaSendRawTransactionTest {
when(privacyController.validatePrivateTransaction(
any(PrivateTransaction.class), any(String.class)))
.thenReturn(ValidationResult.valid());
when(privacyController.retrieveOnChainPrivacyGroup(any(Bytes.class), any(String.class)))
.thenReturn(
Optional.of(
new PrivacyGroup("", PrivacyGroup.Type.ONCHAIN, "", "", Collections.emptyList())));
final Optional<PrivacyGroup> optionalPrivacyGroup =
Optional.of(
new PrivacyGroup(
"", PrivacyGroup.Type.ONCHAIN, "", "", Arrays.asList(ENCLAVE_PUBLIC_KEY)));
when(privacyController.retrieveOnChainPrivacyGroupWithToBeAddedMembers(any(), any(), any()))
.thenReturn(optionalPrivacyGroup);
when(privacyController.buildAndSendAddPayload(
any(PrivateTransaction.class), any(Bytes32.class), any(String.class)))
.thenReturn(Optional.of(ENCLAVE_PUBLIC_KEY));
@ -321,7 +323,7 @@ public class EeaSendRawTransactionTest {
new EeaSendRawTransaction(
transactionPool, privacyController, enclavePublicKeyProvider, true);
when(privacyController.retrieveOnChainPrivacyGroup(any(Bytes.class), any(String.class)))
when(privacyController.retrieveOnChainPrivacyGroupWithToBeAddedMembers(any(), any(), any()))
.thenReturn(Optional.empty());
final JsonRpcRequestContext request =

@ -183,7 +183,8 @@ public abstract class MainnetPrecompiledContractRegistries {
accountVersion,
new PrivacyPrecompiledContract(
precompiledContractConfiguration.getGasCalculator(),
precompiledContractConfiguration.getPrivacyParameters()));
precompiledContractConfiguration.getPrivacyParameters(),
"Privacy"));
}
}
}

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet.precompiles.privacy;
import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.chain.Blockchain;
@ -31,6 +32,7 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.Restriction;
@ -38,11 +40,17 @@ import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.ethereum.vm.GasCalculator;
import org.hyperledger.besu.ethereum.vm.MessageFrame;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
@ -61,12 +69,21 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
public OnChainPrivacyPrecompiledContract(
final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) {
super(gasCalculator, privacyParameters, "OnChainPrivacy");
}
public OnChainPrivacyPrecompiledContract(
final GasCalculator gasCalculator,
final Enclave enclave,
final WorldStateArchive worldStateArchive,
final PrivateStateStorage privateStateStorage,
final PrivateStateRootResolver privateStateRootResolver) {
super(
gasCalculator,
privacyParameters.getEnclave(),
privacyParameters.getPrivateWorldStateArchive(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateStateRootResolver(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver,
"OnChainPrivacy");
}
@ -97,13 +114,11 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
final PrivateTransaction privateTransaction =
versionedPrivateTransaction.getPrivateTransaction();
if (!privateFromMatchesSenderKey(
privateTransaction.getPrivateFrom(), receiveResponse.getSenderKey())) {
final Bytes privateFrom = privateTransaction.getPrivateFrom();
if (!privateFromMatchesSenderKey(privateFrom, receiveResponse.getSenderKey())) {
return Bytes.EMPTY;
}
final Bytes32 version = versionedPrivateTransaction.getVersion();
final Optional<Bytes> maybeGroupId = privateTransaction.getPrivacyGroupId();
if (maybeGroupId.isEmpty()) {
return Bytes.EMPTY;
@ -124,22 +139,23 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
final WorldUpdater privateWorldStateUpdater = disposablePrivateState.updater();
final WorldUpdater publicWorldState = messageFrame.getWorldState();
final Blockchain blockchain = messageFrame.getBlockchain();
maybeInjectDefaultManagementAndProxy(
lastRootHash, disposablePrivateState, privateWorldStateUpdater);
final Blockchain blockchain = messageFrame.getBlockchain();
final WorldUpdater publicWorldState = messageFrame.getWorldState();
if (!canExecute(
messageFrame,
currentBlockHeader,
privateTransaction,
version,
versionedPrivateTransaction.getVersion(),
publicWorldState,
privacyGroupId,
blockchain,
disposablePrivateState,
privateWorldStateUpdater)) {
privateWorldStateUpdater,
privateFrom)) {
return Bytes.EMPTY;
}
@ -164,7 +180,6 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
persistPrivateState(
pmtHash,
currentBlockHash,
privateTransaction,
privacyGroupId,
disposablePrivateState,
privateWorldStateUpdater,
@ -183,13 +198,14 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
final Bytes32 privacyGroupId,
final Blockchain blockchain,
final MutableWorldState disposablePrivateState,
final WorldUpdater privateWorldStateUpdater) {
final WorldUpdater privateWorldStateUpdater,
final Bytes privateFrom) {
final boolean isAddingParticipant =
privateTransaction
.getPayload()
.toHexString()
.startsWith(OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE.toHexString());
.startsWith(OnChainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString());
final boolean isPrivacyGroupLocked =
isContractLocked(
@ -226,11 +242,97 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
blockchain,
disposablePrivateState,
privateWorldStateUpdater)) {
LOG.debug(
"Privacy group version mismatch while trying to execute transaction with commitment {}",
messageFrame.getTransactionHash());
return false;
}
if (!isMemberOfPrivacyGroup(
isAddingParticipant,
privateTransaction,
privateFrom,
privacyGroupId,
messageFrame,
currentBlockHeader,
publicWorldState,
blockchain,
disposablePrivateState,
privateWorldStateUpdater)) {
LOG.debug(
"PrivateTransaction with hash {} cannot execute in privacy group {} because privateFrom {} is not a member.",
messageFrame.getTransactionHash(),
privacyGroupId.toBase64String(),
privateFrom.toBase64String());
return false;
}
return true;
}
private boolean isMemberOfPrivacyGroup(
final boolean isAddingParticipant,
final PrivateTransaction privateTransaction,
final Bytes privateFrom,
final Bytes32 privacyGroupId,
final MessageFrame messageFrame,
final ProcessableBlockHeader currentBlockHeader,
final WorldUpdater publicWorldState,
final Blockchain blockchain,
final MutableWorldState disposablePrivateState,
final WorldUpdater privateWorldStateUpdater) {
final PrivateTransactionProcessor.Result result =
simulateTransaction(
messageFrame,
currentBlockHeader,
publicWorldState,
privacyGroupId,
blockchain,
disposablePrivateState,
privateWorldStateUpdater,
OnChainGroupManagement.GET_PARTICIPANTS_METHOD_SIGNATURE);
final List<Bytes> list = getMembersFromResult(result);
List<String> participantsFromParameter = Collections.emptyList();
if (list.isEmpty() && isAddingParticipant) {
// creating a new group, so we are checking whether the privateFrom is one of the members of
// the new group
participantsFromParameter = getParticipantsFromParameter(privateTransaction.getPayload());
}
return list.contains(privateFrom)
|| participantsFromParameter.contains(privateFrom.toBase64String());
}
List<Bytes> getMembersFromResult(final PrivateTransactionProcessor.Result result) {
List<Bytes> list = Collections.emptyList();
if (result != null && result.isSuccessful()) {
final RLPInput rlpInput = RLP.input(result.getOutput());
if (rlpInput.nextSize() > 0) {
list = decodeList(rlpInput.raw());
}
}
return list;
}
// TODO: this method is copied from DefaultPrivacyController. Fix the duplication in a separate GI
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 List<Bytes> decodeList(final Bytes rlpEncodedList) {
final ArrayList<Bytes> 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))); // participant
}
return decodedElements;
}
protected boolean isContractLocked(
final MessageFrame messageFrame,
final ProcessableBlockHeader currentBlockHeader,

@ -60,14 +60,16 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
private static final Logger LOG = LogManager.getLogger();
public PrivacyPrecompiledContract(
final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) {
final GasCalculator gasCalculator,
final PrivacyParameters privacyParameters,
final String name) {
this(
gasCalculator,
privacyParameters.getEnclave(),
privacyParameters.getPrivateWorldStateArchive(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateStateRootResolver(),
"Privacy");
name);
}
@VisibleForTesting
@ -134,14 +136,33 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
final PrivateTransaction privateTransaction =
PrivateTransaction.readFrom(bytesValueRLPInput.readAsRlp());
if (!privateFromMatchesSenderKey(
privateTransaction.getPrivateFrom(), receiveResponse.getSenderKey())) {
final Bytes privateFrom = privateTransaction.getPrivateFrom();
if (!privateFromMatchesSenderKey(privateFrom, receiveResponse.getSenderKey())) {
return Bytes.EMPTY;
}
final Bytes32 privacyGroupId =
Bytes32.wrap(Bytes.fromBase64String(receiveResponse.getPrivacyGroupId()));
try {
if (privateTransaction.getPrivateFor().isEmpty()
&& !enclave
.retrievePrivacyGroup(privacyGroupId.toBase64String())
.getMembers()
.contains(privateFrom.toBase64String())) {
return Bytes.EMPTY;
}
} catch (final EnclaveClientException e) {
// This exception is thrown when the privacy group can not be found
return Bytes.EMPTY;
} catch (final EnclaveServerException e) {
LOG.error("Enclave is responding with an error, perhaps it has a misconfiguration?", e);
throw e;
} catch (final EnclaveIOException e) {
LOG.error("Can not communicate with enclave, is it up?", e);
throw e;
}
LOG.debug("Processing private transaction {} in privacy group {}", pmtHash, privacyGroupId);
final Hash currentBlockHash = ((BlockHeader) messageFrame.getBlockHeader()).getHash();
@ -175,7 +196,6 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
persistPrivateState(
pmtHash,
currentBlockHash,
privateTransaction,
privacyGroupId,
disposablePrivateState,
privateWorldStateUpdater,
@ -188,7 +208,6 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
void persistPrivateState(
final Hash commitmentHash,
final Hash currentBlockHash,
final PrivateTransaction privateTransaction,
final Bytes32 privacyGroupId,
final MutableWorldState disposablePrivateState,
final WorldUpdater privateWorldStateUpdater,

@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.privacy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE;
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;
@ -272,8 +272,7 @@ public class DefaultPrivacyController implements PrivacyController {
.keySet()
.forEach(
c -> {
final Optional<PrivacyGroup> maybePrivacyGroup =
retrieveOnChainPrivacyGroup(c, enclavePublicKey);
final Optional<PrivacyGroup> maybePrivacyGroup = retrieveOnChainPrivacyGroup(c);
if (maybePrivacyGroup.isPresent()
&& maybePrivacyGroup.get().getMembers().containsAll(addresses)) {
privacyGroups.add(maybePrivacyGroup.get());
@ -282,15 +281,11 @@ public class DefaultPrivacyController implements PrivacyController {
return privacyGroups;
}
@Override
public Optional<PrivacyGroup> retrieveOnChainPrivacyGroup(
final Bytes privacyGroupId, final String enclavePublicKey) {
public Optional<PrivacyGroup> retrieveOnChainPrivacyGroup(final Bytes privacyGroupId) {
// get the privateFor list from the management contract
final Optional<PrivateTransactionProcessor.Result> privateTransactionSimulatorResultOptional =
privateTransactionSimulator.process(
privacyGroupId.toBase64String(),
buildCallParams(
Bytes.fromBase64String(enclavePublicKey), GET_PARTICIPANTS_METHOD_SIGNATURE));
privacyGroupId.toBase64String(), buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE));
if (privateTransactionSimulatorResultOptional.isPresent()
&& privateTransactionSimulatorResultOptional.get().isSuccessful()) {
@ -307,8 +302,45 @@ public class DefaultPrivacyController implements PrivacyController {
} else {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
@Override
public Optional<PrivacyGroup> retrieveOnChainPrivacyGroupWithToBeAddedMembers(
final Bytes privacyGroupId,
final String enclavePublicKey,
final PrivateTransaction privateTransaction) {
// get the privateFor list from the management contract
final Optional<PrivateTransactionProcessor.Result> 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(enclavePublicKey)) {
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));
}
return Optional.empty();
}
private List<String> decodeList(final Bytes rlpEncodedList) {
@ -324,21 +356,16 @@ public class DefaultPrivacyController implements PrivacyController {
private List<String> getParticipantsFromParameter(final Bytes input) {
final List<String> participants = new ArrayList<>();
final Bytes mungedParticipants = input.slice(4 + 32 + 32 + 32);
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 enclavePublicKey, final Bytes methodCall) {
private CallParameter buildCallParams(final Bytes methodCall) {
return new CallParameter(
Address.ZERO,
Address.ONCHAIN_PRIVACY_PROXY,
3000000,
Wei.of(1000),
Wei.ZERO,
Bytes.concatenate(methodCall, enclavePublicKey));
Address.ZERO, Address.ONCHAIN_PRIVACY_PROXY, 3000000, Wei.of(1000), Wei.ZERO, methodCall);
}
private List<PrivateTransactionMetadata> buildTransactionMetadataList(
@ -411,7 +438,7 @@ public class DefaultPrivacyController implements PrivacyController {
&& privateTransaction
.getPayload()
.toHexString()
.startsWith(ADD_TO_GROUP_METHOD_SIGNATURE.toHexString());
.startsWith(ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString());
}
@Override
@ -448,23 +475,21 @@ public class DefaultPrivacyController implements PrivacyController {
final Optional<PrivacyGroup> maybePrivacyGroup) {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
if (maybePrivacyGroup.isPresent() || isGroupAdditionTransaction(privateTransaction)) {
if (isGroupAdditionTransaction(privateTransaction)
|| maybePrivacyGroup.get().getType() == PrivacyGroup.Type.ONCHAIN) {
if (maybePrivacyGroup.isPresent()) {
final PrivacyGroup privacyGroup = maybePrivacyGroup.get();
if (privacyGroup.getType() == PrivacyGroup.Type.ONCHAIN) {
// onchain privacy group
final Optional<PrivateTransactionProcessor.Result> result =
privateTransactionSimulator.process(
privateTransaction.getPrivacyGroupId().get().toBase64String(),
buildCallParams(
Bytes.fromBase64String(enclavePublicKey), GET_VERSION_METHOD_SIGNATURE));
buildCallParams(GET_VERSION_METHOD_SIGNATURE));
new VersionedPrivateTransaction(privateTransaction, result).writeTo(rlpOutput);
final List<String> onChainPrivateFor =
resolveOnChainPrivateFor(privateTransaction, maybePrivacyGroup);
final List<String> onChainPrivateFor = privacyGroup.getMembers();
return enclave.send(
rlpOutput.encoded().toBase64String(),
privateTransaction.getPrivateFrom().toBase64String(),
onChainPrivateFor);
} else if (maybePrivacyGroup.get().getType() == PrivacyGroup.Type.PANTHEON) {
} else if (privacyGroup.getType() == PrivacyGroup.Type.PANTHEON) {
// offchain privacy group
privateTransaction.writeTo(rlpOutput);
return enclave.send(
@ -499,18 +524,6 @@ public class DefaultPrivacyController implements PrivacyController {
return privateFor;
}
private List<String> resolveOnChainPrivateFor(
final PrivateTransaction privateTransaction, final Optional<PrivacyGroup> privacyGroup) {
final ArrayList<String> privateFor = new ArrayList<>();
if (isGroupAdditionTransaction(privateTransaction)) {
privateFor.addAll(getParticipantsFromParameter(privateTransaction.getPayload()));
}
if (privacyGroup.isPresent()) {
privateFor.addAll(privacyGroup.get().getMembers());
}
return privateFor;
}
@Override
public void verifyPrivacyGroupContainsEnclavePublicKey(
final String privacyGroupId, final String enclavePublicKey) {

@ -230,15 +230,15 @@ public class MultiTenancyPrivacyController implements PrivacyController {
}
@Override
public Optional<PrivacyGroup> retrieveOnChainPrivacyGroup(
final Bytes privacyGroupId, final String enclavePublicKey) {
public Optional<PrivacyGroup> retrieveOnChainPrivacyGroupWithToBeAddedMembers(
final Bytes privacyGroupId,
final String enclavePublicKey,
final PrivateTransaction privateTransaction) {
final Optional<PrivacyGroup> maybePrivacyGroup =
privacyController.retrieveOnChainPrivacyGroup(privacyGroupId, enclavePublicKey);
if (maybePrivacyGroup.isPresent()
&& !maybePrivacyGroup.get().getMembers().contains(enclavePublicKey)) {
throw new MultiTenancyValidationException(
"Privacy group must contain the enclave public key");
}
privacyController.retrieveOnChainPrivacyGroupWithToBeAddedMembers(
privacyGroupId, enclavePublicKey, privateTransaction);
// The check that the enclavePublicKey is a member (if the group already exists) is done in the
// DefaultPrivacyController.
return maybePrivacyGroup;
}

@ -83,7 +83,8 @@ public interface PrivacyController {
final Hash blockHash,
final String enclavePublicKey);
Optional<PrivacyGroup> retrieveOnChainPrivacyGroup(Bytes privacyGroupId, String enclavePublicKey);
Optional<PrivacyGroup> retrieveOnChainPrivacyGroupWithToBeAddedMembers(
Bytes privacyGroupId, String enclavePublicKey, final PrivateTransaction privateTransaction);
List<PrivateTransactionWithMetadata> retrieveAddBlob(String addDataKey);

@ -48,8 +48,7 @@ public class PrivateTransactionSimulator {
private static final SECP256K1.Signature FAKE_SIGNATURE =
SECP256K1.Signature.create(SECP256K1.HALF_CURVE_ORDER, SECP256K1.HALF_CURVE_ORDER, (byte) 0);
private static final Address DEFAULT_FROM =
Address.fromHexString("0x0000000000000000000000000000000000000000");
private static final Address DEFAULT_FROM = Address.ZERO;
private final Blockchain blockchain;
private final WorldStateArchive worldStateArchive;

@ -116,6 +116,12 @@ public class PrivateTransactionDataFixture {
.createTransaction(KEY_PAIR);
}
public static VersionedPrivateTransaction versionedPrivateTransactionBesu() {
return new PrivateTransactionTestFixture()
.privacyGroupId(VALID_BASE64_ENCLAVE_KEY)
.createVersionedPrivateTransaction((KEY_PAIR));
}
public static PrivateTransaction privateContractDeploymentTransactionBesu() {
return new PrivateTransactionTestFixture()
.payload(VALID_CONTRACT_DEPLOYMENT_PAYLOAD)

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
import java.math.BigInteger;
import java.util.List;
@ -24,6 +25,7 @@ import java.util.Optional;
import com.google.common.collect.Lists;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class PrivateTransactionTestFixture {
@ -84,6 +86,11 @@ public class PrivateTransactionTestFixture {
return builder.signAndBuild(keys);
}
public VersionedPrivateTransaction createVersionedPrivateTransaction(final KeyPair keyPair) {
final PrivateTransaction transaction = createTransaction(keyPair);
return new VersionedPrivateTransaction(transaction, Bytes32.ZERO);
}
public PrivateTransactionTestFixture nonce(final long nonce) {
this.nonce = nonce;
return this;

@ -0,0 +1,314 @@
/*
* 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.mainnet.precompiles.privacy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.versionedPrivateTransactionBesu;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveConfigurationException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Log;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.mainnet.SpuriousDragonGasCalculator;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.MessageFrame;
import org.hyperledger.besu.ethereum.vm.OperationTracer;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
public class OnChainPrivacyPrecompiledContractTest {
@Rule public final TemporaryFolder temp = new TemporaryFolder();
private final Bytes txEnclaveKey = Bytes.random(32);
private MessageFrame messageFrame;
private Blockchain blockchain;
private final String DEFAULT_OUTPUT = "0x01";
final String PAYLOAD_TEST_PRIVACY_GROUP_ID = "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o=";
private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class);
final PrivateStateRootResolver privateStateRootResolver =
new PrivateStateRootResolver(privateStateStorage);
private PrivateTransactionProcessor mockPrivateTxProcessor(
final PrivateTransactionProcessor.Result result) {
final PrivateTransactionProcessor mockPrivateTransactionProcessor =
mock(PrivateTransactionProcessor.class);
when(mockPrivateTransactionProcessor.processTransaction(
nullable(Blockchain.class),
nullable(WorldUpdater.class),
nullable(WorldUpdater.class),
nullable(ProcessableBlockHeader.class),
nullable((Hash.class)),
nullable(PrivateTransaction.class),
nullable(Address.class),
nullable(OperationTracer.class),
nullable(BlockHashLookup.class),
nullable(Bytes.class)))
.thenReturn(result);
return mockPrivateTransactionProcessor;
}
@Before
public void setUp() {
final MutableWorldState mutableWorldState = mock(MutableWorldState.class);
when(mutableWorldState.updater()).thenReturn(mock(WorldUpdater.class));
when(worldStateArchive.getMutable()).thenReturn(mutableWorldState);
when(worldStateArchive.getMutable(any())).thenReturn(Optional.of(mutableWorldState));
final PrivateStateStorage.Updater storageUpdater = mock(PrivateStateStorage.Updater.class);
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any()))
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY));
when(privateStateStorage.getPrivateBlockMetadata(any(), any())).thenReturn(Optional.empty());
when(storageUpdater.putPrivateBlockMetadata(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(storageUpdater.putPrivacyGroupHeadBlockMap(nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(storageUpdater.putTransactionReceipt(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(privateStateStorage.updater()).thenReturn(storageUpdater);
messageFrame = mock(MessageFrame.class);
blockchain = mock(Blockchain.class);
final BlockDataGenerator blockGenerator = new BlockDataGenerator();
final Block genesis = blockGenerator.genesisBlock();
final Block block =
blockGenerator.block(
new BlockDataGenerator.BlockOptions().setParentHash(genesis.getHeader().getHash()));
when(blockchain.getGenesisBlock()).thenReturn(genesis);
when(blockchain.getBlockByHash(block.getHash())).thenReturn(Optional.of(block));
when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis));
when(messageFrame.getBlockchain()).thenReturn(blockchain);
when(messageFrame.getBlockHeader()).thenReturn(block.getHeader());
}
@Test
public void testPayloadFoundInEnclave() {
final Enclave enclave = mock(Enclave.class);
final OnChainPrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
final List<Log> logs = new ArrayList<>();
contract.setPrivateTransactionProcessor(
mockPrivateTxProcessor(
PrivateTransactionProcessor.Result.successful(
logs, 0, 0, Bytes.fromHexString(DEFAULT_OUTPUT), null)));
final VersionedPrivateTransaction versionedPrivateTransaction =
versionedPrivateTransactionBesu();
final byte[] payload = convertVersionedPrivateTransactionToBytes(versionedPrivateTransaction);
final String privateFrom =
versionedPrivateTransaction.getPrivateTransaction().getPrivateFrom().toBase64String();
final ReceiveResponse response =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, privateFrom);
when(enclave.receive(any())).thenReturn(response);
final OnChainPrivacyPrecompiledContract contractSpy = spy(contract);
Mockito.doNothing().when(contractSpy).maybeInjectDefaultManagementAndProxy(any(), any(), any());
Mockito.doReturn(true)
.when(contractSpy)
.canExecute(any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
final Bytes actual = contractSpy.compute(txEnclaveKey, messageFrame);
assertThat(actual).isEqualTo(Bytes.fromHexString(DEFAULT_OUTPUT));
}
@Test
public void testPayloadNotFoundInEnclave() {
final Enclave enclave = mock(Enclave.class);
final PrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
when(enclave.receive(any(String.class))).thenThrow(EnclaveClientException.class);
final Bytes expected = contract.compute(txEnclaveKey, messageFrame);
assertThat(expected).isEqualTo(Bytes.EMPTY);
}
@Test(expected = RuntimeException.class)
public void testEnclaveDown() {
final Enclave enclave = mock(Enclave.class);
final PrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
when(enclave.receive(any(String.class))).thenThrow(new RuntimeException());
contract.compute(txEnclaveKey, messageFrame);
}
@Test
public void testEnclaveBelowRequiredVersion() {
final Enclave enclave = mock(Enclave.class);
final OnChainPrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
final VersionedPrivateTransaction versionedPrivateTransaction =
versionedPrivateTransactionBesu();
final byte[] payload = convertVersionedPrivateTransactionToBytes(versionedPrivateTransaction);
final ReceiveResponse responseWithoutSenderKey =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, null);
when(enclave.receive(eq(txEnclaveKey.toBase64String()))).thenReturn(responseWithoutSenderKey);
assertThatThrownBy(() -> contract.compute(txEnclaveKey, messageFrame))
.isInstanceOf(EnclaveConfigurationException.class)
.hasMessage("Incompatible Orion version. Orion version must be 1.6.0 or greater.");
}
@Test
public void testPayloadNotMatchingPrivateFrom() {
final Enclave enclave = mock(Enclave.class);
final OnChainPrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
final VersionedPrivateTransaction versionedPrivateTransaction =
versionedPrivateTransactionBesu();
final byte[] payload = convertVersionedPrivateTransactionToBytes(versionedPrivateTransaction);
final String wrongSenderKey = Bytes.random(32).toBase64String();
final ReceiveResponse responseWithWrongSenderKey =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, wrongSenderKey);
when(enclave.receive(eq(txEnclaveKey.toBase64String()))).thenReturn(responseWithWrongSenderKey);
final Bytes expected = contract.compute(txEnclaveKey, messageFrame);
assertThat(expected).isEqualTo(Bytes.EMPTY);
}
@Test
public void testPrivateFromNotMemberOfGroup() {
final Enclave enclave = mock(Enclave.class);
final OnChainPrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
final OnChainPrivacyPrecompiledContract contractSpy = spy(contract);
Mockito.doNothing().when(contractSpy).maybeInjectDefaultManagementAndProxy(any(), any(), any());
Mockito.doReturn(false)
.when(contractSpy)
.isContractLocked(any(), any(), any(), any(), any(), any(), any());
Mockito.doReturn(true)
.when(contractSpy)
.onChainPrivacyGroupVersionMatches(any(), any(), any(), any(), any(), any(), any(), any());
final PrivateTransactionProcessor.Result mockResult =
mock(PrivateTransactionProcessor.Result.class);
Mockito.doReturn(mockResult)
.when(contractSpy)
.simulateTransaction(any(), any(), any(), any(), any(), any(), any(), any());
Mockito.doReturn(Arrays.asList(Bytes.ofUnsignedInt(1L)))
.when(contractSpy)
.getMembersFromResult(any());
final VersionedPrivateTransaction privateTransaction = versionedPrivateTransactionBesu();
final byte[] payload = convertVersionedPrivateTransactionToBytes(privateTransaction);
final String privateFrom =
privateTransaction.getPrivateTransaction().getPrivateFrom().toBase64String();
final ReceiveResponse response =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, privateFrom);
when(enclave.receive(any(String.class))).thenReturn(response);
final Bytes actual = contractSpy.compute(txEnclaveKey, messageFrame);
assertThat(actual).isEqualTo(Bytes.EMPTY);
}
@Test
public void testInvalidPrivateTransaction() {
final Enclave enclave = mock(Enclave.class);
final OnChainPrivacyPrecompiledContract contract =
new OnChainPrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
contract.setPrivateTransactionProcessor(
mockPrivateTxProcessor(
PrivateTransactionProcessor.Result.invalid(
ValidationResult.invalid(
TransactionValidator.TransactionInvalidReason.INCORRECT_NONCE))));
final OnChainPrivacyPrecompiledContract contractSpy = spy(contract);
Mockito.doNothing().when(contractSpy).maybeInjectDefaultManagementAndProxy(any(), any(), any());
Mockito.doReturn(true)
.when(contractSpy)
.canExecute(any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
final VersionedPrivateTransaction privateTransaction = versionedPrivateTransactionBesu();
final byte[] payload = convertVersionedPrivateTransactionToBytes(privateTransaction);
final String privateFrom =
privateTransaction.getPrivateTransaction().getPrivateFrom().toBase64String();
final ReceiveResponse response =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, privateFrom);
when(enclave.receive(any(String.class))).thenReturn(response);
final Bytes actual = contractSpy.compute(txEnclaveKey, messageFrame);
assertThat(actual).isEqualTo(Bytes.EMPTY);
}
private byte[] convertVersionedPrivateTransactionToBytes(
final VersionedPrivateTransaction privateTransaction) {
final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput();
privateTransaction.writeTo(bytesValueRLPOutput);
return bytesValueRLPOutput.encoded().toBase64String().getBytes(UTF_8);
}
private OnChainPrivacyPrecompiledContract buildPrivacyPrecompiledContract(final Enclave enclave) {
return new OnChainPrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
}
}

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet.precompiles.privacy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.VALID_BASE64_ENCLAVE_KEY;
import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.privateTransactionBesu;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@ -28,6 +29,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveConfigurationException;
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.core.Address;
@ -53,6 +55,7 @@ import org.hyperledger.besu.ethereum.vm.OperationTracer;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@ -136,6 +139,14 @@ public class PrivacyPrecompiledContractTest {
@Test
public void testPayloadFoundInEnclave() {
final Enclave enclave = mock(Enclave.class);
when(enclave.retrievePrivacyGroup(PAYLOAD_TEST_PRIVACY_GROUP_ID))
.thenReturn(
new PrivacyGroup(
PAYLOAD_TEST_PRIVACY_GROUP_ID,
PrivacyGroup.Type.PANTHEON,
"",
"",
Arrays.asList(VALID_BASE64_ENCLAVE_KEY.toBase64String())));
final PrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
final List<Log> logs = new ArrayList<>();
contract.setPrivateTransactionProcessor(
@ -144,7 +155,7 @@ public class PrivacyPrecompiledContractTest {
logs, 0, 0, Bytes.fromHexString(DEFAULT_OUTPUT), null)));
final PrivateTransaction privateTransaction = privateTransactionBesu();
byte[] payload = convertPrivateTransactionToBytes(privateTransaction);
final byte[] payload = convertPrivateTransactionToBytes(privateTransaction);
final String privateFrom = privateTransaction.getPrivateFrom().toBase64String();
final ReceiveResponse response =
@ -226,9 +237,47 @@ public class PrivacyPrecompiledContractTest {
assertThat(expected).isEqualTo(Bytes.EMPTY);
}
@Test
public void testPrivateFromNotMemberOfGroup() {
final Enclave enclave = mock(Enclave.class);
when(enclave.retrievePrivacyGroup(PAYLOAD_TEST_PRIVACY_GROUP_ID))
.thenReturn(
new PrivacyGroup(
PAYLOAD_TEST_PRIVACY_GROUP_ID,
PrivacyGroup.Type.PANTHEON,
"",
"",
Arrays.asList(VALID_BASE64_ENCLAVE_KEY.toBase64String())));
final PrivacyPrecompiledContract contract = buildPrivacyPrecompiledContract(enclave);
contract.setPrivateTransactionProcessor(
mockPrivateTxProcessor(
PrivateTransactionProcessor.Result.successful(
new ArrayList<>(), 0, 0, Bytes.fromHexString(DEFAULT_OUTPUT), null)));
final PrivateTransaction privateTransaction = privateTransactionBesu();
final byte[] payload = convertPrivateTransactionToBytes(privateTransaction);
final String privateFrom = privateTransaction.getPrivateFrom().toBase64String();
final ReceiveResponse response =
new ReceiveResponse(payload, PAYLOAD_TEST_PRIVACY_GROUP_ID, privateFrom);
when(enclave.receive(any(String.class))).thenReturn(response);
final Bytes actual = contract.compute(txEnclaveKey, messageFrame);
assertThat(actual).isEqualTo(Bytes.fromHexString(DEFAULT_OUTPUT));
}
@Test
public void testInvalidPrivateTransaction() {
final Enclave enclave = mock(Enclave.class);
when(enclave.retrievePrivacyGroup(PAYLOAD_TEST_PRIVACY_GROUP_ID))
.thenReturn(
new PrivacyGroup(
PAYLOAD_TEST_PRIVACY_GROUP_ID,
PrivacyGroup.Type.PANTHEON,
"",
"",
Arrays.asList(VALID_BASE64_ENCLAVE_KEY.toBase64String())));
final PrivacyPrecompiledContract contract =
new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(),

@ -44,7 +44,7 @@ import org.web3j.tx.gas.ContractGasProvider;
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the <a
* href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
*
* <p>Generated with web3j version 4.5.15.
* <p>Generated with web3j version 4.5.16.
*/
@SuppressWarnings("rawtypes")
public class OnChainPrivacyGroupManagementInterface extends Contract {
@ -54,6 +54,8 @@ public class OnChainPrivacyGroupManagementInterface extends Contract {
public static final String FUNC_CANEXECUTE = "canExecute";
public static final String FUNC_CANUPGRADE = "canUpgrade";
public static final String FUNC_GETPARTICIPANTS = "getParticipants";
public static final String FUNC_GETVERSION = "getVersion";
@ -100,13 +102,11 @@ public class OnChainPrivacyGroupManagementInterface extends Contract {
super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider);
}
public RemoteFunctionCall<TransactionReceipt> addParticipants(
byte[] enclaveKey, List<byte[]> participants) {
public RemoteFunctionCall<TransactionReceipt> addParticipants(List<byte[]> participants) {
final Function function =
new Function(
FUNC_ADDPARTICIPANTS,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey),
new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
org.web3j.abi.datatypes.generated.Bytes32.class,
org.web3j.abi.Utils.typeMap(
@ -124,11 +124,18 @@ public class OnChainPrivacyGroupManagementInterface extends Contract {
return executeRemoteCallSingleValueReturn(function, Boolean.class);
}
public RemoteFunctionCall<List> getParticipants(byte[] enclaveKey) {
public RemoteFunctionCall<TransactionReceipt> canUpgrade() {
final Function function =
new Function(
FUNC_CANUPGRADE, Arrays.<Type>asList(), Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<List> getParticipants() {
final Function function =
new Function(
FUNC_GETPARTICIPANTS,
Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey)),
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<DynamicArray<Bytes32>>() {}));
return new RemoteFunctionCall<List>(
function,
@ -157,14 +164,11 @@ public class OnChainPrivacyGroupManagementInterface extends Contract {
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<TransactionReceipt> removeParticipant(
byte[] enclaveKey, byte[] account) {
public RemoteFunctionCall<TransactionReceipt> removeParticipant(byte[] account) {
final Function function =
new Function(
FUNC_REMOVEPARTICIPANT,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey),
new org.web3j.abi.datatypes.generated.Bytes32(account)),
Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(account)),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}

@ -3,6 +3,7 @@ import "./OnChainPrivacyGroupManagementInterface.sol";
contract DefaultOnChainPrivacyGroupManagementContract is OnChainPrivacyGroupManagementInterface {
address private _owner;
bool private _canExecute;
bytes32 private _version;
bytes32[] private distributionList;
@ -12,89 +13,92 @@ contract DefaultOnChainPrivacyGroupManagementContract is OnChainPrivacyGroupMana
return _version;
}
// overrides
function canExecute() external view returns (bool) {
return _canExecute;
}
function lock() public {
require(_canExecute);
require(tx.origin == _owner, "Origin not the owner.");
_canExecute = false;
}
function unlock() public {
require(!_canExecute);
require(tx.origin == _owner, "Origin not the owner.");
_canExecute = true;
}
function addParticipants(bytes32 _enclaveKey, bytes32[] memory _accounts) public returns (bool) {
function addParticipants(bytes32[] memory _publicEnclaveKeys) public returns (bool) {
require(!_canExecute);
if(distributionList.length == 0) {
addParticipant(_enclaveKey);
if (_owner == address(0x0)) {
// The account creating this group is set to be the owner
_owner = tx.origin;
}
require(isMember(_enclaveKey));
bool result = addAll(_enclaveKey, _accounts);
require(tx.origin == _owner, "Origin not the owner.");
bool result = addAll(_publicEnclaveKeys);
_canExecute = true;
updateVersion();
return result;
}
function removeParticipant(bytes32 _enclaveKey, bytes32 _account) public returns (bool) {
require(isMember(_enclaveKey));
bool result = removeInternal(_account);
function removeParticipant(bytes32 _member) public returns (bool) {
require(_canExecute);
require(tx.origin == _owner, "Origin not the owner.");
bool result = removeInternal(_member);
updateVersion();
return result;
}
function getParticipants(bytes32 _enclaveKey) public view returns (bytes32[] memory) {
require(isMember(_enclaveKey));
function getParticipants() public view returns (bytes32[] memory) {
return distributionList;
}
function canUpgrade() external returns (bool) {
return tx.origin == _owner;
}
//internal functions
function addAll(bytes32 _enclaveKey, bytes32[] memory _accounts) internal returns (bool) {
function addAll(bytes32[] memory _publicEnclaveKeys) internal returns (bool) {
bool allAdded = true;
for (uint i = 0; i < _accounts.length; i++) {
if (_enclaveKey == _accounts[i]) {
emit ParticipantAdded(false, _accounts[i], "Adding own account as a Member is not permitted");
allAdded = allAdded && false;
} else if (isMember(_accounts[i])) {
emit ParticipantAdded(false, _accounts[i], "Account is already a Member");
for (uint i = 0; i < _publicEnclaveKeys.length; i++) {
if (isMember(_publicEnclaveKeys[i])) {
emit ParticipantAdded(false, _publicEnclaveKeys[i], "Account is already a Member");
allAdded = allAdded && false;
} else {
bool result = addParticipant(_accounts[i]);
bool result = addParticipant(_publicEnclaveKeys[i]);
string memory message = result ? "Member account added successfully" : "Account is already a Member";
emit ParticipantAdded(result, _accounts[i], message);
emit ParticipantAdded(result, _publicEnclaveKeys[i], message);
allAdded = allAdded && result;
}
}
return allAdded;
}
function isMember(bytes32 _account) internal view returns (bool) {
return distributionIndexOf[_account] != 0;
function isMember(bytes32 _publicEnclaveKey) internal view returns (bool) {
return distributionIndexOf[_publicEnclaveKey] != 0;
}
function addParticipant(bytes32 _participant) internal returns (bool) {
if (distributionIndexOf[_participant] == 0) {
distributionIndexOf[_participant] = distributionList.push(_participant);
function addParticipant(bytes32 _publicEnclaveKey) internal returns (bool) {
if (distributionIndexOf[_publicEnclaveKey] == 0) {
distributionIndexOf[_publicEnclaveKey] = distributionList.push(_publicEnclaveKey);
return true;
}
return false;
}
function removeInternal(bytes32 _participant) internal returns (bool) {
uint256 index = distributionIndexOf[_participant];
function removeInternal(bytes32 _member) internal returns (bool) {
uint256 index = distributionIndexOf[_member];
if (index > 0 && index <= distributionList.length) {
//move last address into index being vacated (unless we are dealing with last index)
if (index != distributionList.length) {
bytes32 lastAccount = distributionList[distributionList.length - 1];
distributionList[index - 1] = lastAccount;
distributionIndexOf[lastAccount] = index;
bytes32 lastPublicKey = distributionList[distributionList.length - 1];
distributionList[index - 1] = lastPublicKey;
distributionIndexOf[lastPublicKey] = index;
}
distributionList.length -= 1;
distributionIndexOf[_participant] = 0;
distributionIndexOf[_member] = 0;
return true;
}
return false;
@ -105,8 +109,8 @@ contract DefaultOnChainPrivacyGroupManagementContract is OnChainPrivacyGroupMana
}
event ParticipantAdded(
bool adminAdded,
bytes32 account,
bool success,
bytes32 publicEnclaveKey,
string message
);
}

@ -1,12 +1,11 @@
pragma solidity ^0.5.9;
interface OnChainPrivacyGroupManagementInterface {
function addParticipants(bytes32 enclaveKey, bytes32[] calldata participants) external returns (bool);
function addParticipants(bytes32[] calldata participants) external returns (bool);
function removeParticipant(bytes32 enclaveKey, bytes32 account) external returns (bool);
function removeParticipant(bytes32 account) external returns (bool);
function getParticipants(bytes32 enclaveKey) external view returns (bytes32[] memory);
function getParticipants() external view returns (bytes32[] memory);
function lock() external;
@ -15,4 +14,6 @@ interface OnChainPrivacyGroupManagementInterface {
function canExecute() external view returns (bool);
function getVersion() external view returns (bytes32);
}
function canUpgrade() external returns (bool);
}

@ -1,5 +1,6 @@
pragma solidity ^0.5.12;
pragma solidity ^0.5.9;
import "./OnChainPrivacyGroupManagementInterface.sol";
contract OnChainPrivacyGroupManagementProxy is OnChainPrivacyGroupManagementInterface {
@ -10,28 +11,27 @@ contract OnChainPrivacyGroupManagementProxy is OnChainPrivacyGroupManagementInte
implementation = _implementation;
}
function upgradeTo(address _newImplementation) external {
require(implementation != _newImplementation);
_setImplementation(_newImplementation);
}
function _setImplementation(address _newImp) internal {
implementation = _newImp;
}
function addParticipants(bytes32 enclaveKey, bytes32[] memory participants) public returns (bool) {
function addParticipants(bytes32[] memory _publicEnclaveKeys) public returns (bool) {
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
return privacyInterface.addParticipants(enclaveKey, participants);
return privacyInterface.addParticipants(_publicEnclaveKeys);
}
function getParticipants(bytes32 enclaveKey) view public returns (bytes32[] memory) {
function getParticipants() view public returns (bytes32[] memory) {
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
return privacyInterface.getParticipants(enclaveKey);
return privacyInterface.getParticipants();
}
function removeParticipant(bytes32 enclaveKey, bytes32 account) public returns (bool) {
function removeParticipant(bytes32 _member) public returns (bool) {
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
return privacyInterface.removeParticipant(enclaveKey, account);
bool result = privacyInterface.removeParticipant(_member);
if (result) {
emit ParticipantRemoved(_member);
}
return result;
}
function lock() public {
@ -53,4 +53,25 @@ contract OnChainPrivacyGroupManagementProxy is OnChainPrivacyGroupManagementInte
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
return privacyInterface.getVersion();
}
function canUpgrade() external returns (bool) {
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
return privacyInterface.canUpgrade();
}
function upgradeTo(address _newImplementation) external {
require(this.canExecute(), "The contract is locked.");
require(implementation != _newImplementation, "The contract to upgrade to has to be different from the current management contract.");
require(this.canUpgrade(), "Not allowed to upgrade the management contract.");
bytes32[] memory participants = this.getParticipants();
_setImplementation(_newImplementation);
OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation);
privacyInterface.addParticipants(participants);
}
event ParticipantRemoved(
bytes32 publicEnclaveKey
);
}

Loading…
Cancel
Save