diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java index 2b654b7b8..0171f9dac 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java @@ -18,6 +18,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; import org.hyperledger.besu.tests.acceptance.dsl.condition.miner.MiningStatusCondition; import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; +import java.math.BigInteger; +import java.util.List; + public class EthConditions { private final EthTransactions transactions; @@ -68,4 +71,10 @@ public class EthConditions { public Condition miningStatus(final boolean isMining) { return new MiningStatusCondition(transactions.mining(), isMining); } + + public Condition expectNewPendingTransactions( + final BigInteger filterId, final List transactionHashes) { + return new NewPendingTransactionFilterChangesCondition( + transactions.filterChanges(filterId), transactionHashes); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/NewPendingTransactionFilterChangesCondition.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/NewPendingTransactionFilterChangesCondition.java new file mode 100644 index 000000000..1b483dd98 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/NewPendingTransactionFilterChangesCondition.java @@ -0,0 +1,52 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.condition.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; +import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; +import org.hyperledger.besu.tests.acceptance.dsl.node.Node; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthFilterChangesTransaction; + +import java.util.List; + +import org.web3j.protocol.core.methods.response.EthLog; + +public class NewPendingTransactionFilterChangesCondition implements Condition { + + private final EthFilterChangesTransaction filterChanges; + private final List transactionHashes; + + public NewPendingTransactionFilterChangesCondition( + final EthFilterChangesTransaction filterChanges, final List transactionHashes) { + + this.filterChanges = filterChanges; + this.transactionHashes = transactionHashes; + } + + @Override + public void verify(final Node node) { + WaitUtils.waitFor( + () -> { + final EthLog response = node.execute(filterChanges); + assertThat(response).isNotNull(); + assertThat(response.getResult().size()).isEqualTo(transactionHashes.size()); + for (int i = 0; i < transactionHashes.size(); ++i) { + assertThat(response.getLogs().get(0).get()).isEqualTo(transactionHashes.get(0)); + } + }); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java index 296ff6c5c..1fac98fcf 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java @@ -19,15 +19,23 @@ import org.hyperledger.orion.testutil.OrionKeyConfiguration; public class PrivacyNodeConfiguration { + private final int privacyAddress; private final BesuNodeConfiguration besuConfig; private final OrionKeyConfiguration orionConfig; PrivacyNodeConfiguration( - final BesuNodeConfiguration besuConfig, final OrionKeyConfiguration orionConfig) { + final int privacyAddress, + final BesuNodeConfiguration besuConfig, + final OrionKeyConfiguration orionConfig) { + this.privacyAddress = privacyAddress; this.besuConfig = besuConfig; this.orionConfig = orionConfig; } + public int getPrivacyAddress() { + return privacyAddress; + } + public BesuNodeConfiguration getBesuConfig() { return besuConfig; } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java index 43f92787e..ea1ef3d41 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy; +import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfigurationFactory; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory; @@ -41,8 +42,15 @@ public class PrivacyNodeFactory { public PrivacyNode createPrivateTransactionEnabledMinerNode( final String name, final PrivacyAccount privacyAccount) throws IOException { + return createPrivateTransactionEnabledMinerNode(name, privacyAccount, Address.PRIVACY); + } + + public PrivacyNode createPrivateTransactionEnabledMinerNode( + final String name, final PrivacyAccount privacyAccount, final int privacyAddress) + throws IOException { return create( new PrivacyNodeConfiguration( + privacyAddress, new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() @@ -57,8 +65,15 @@ public class PrivacyNodeFactory { public PrivacyNode createPrivateTransactionEnabledNode( final String name, final PrivacyAccount privacyAccount) throws IOException { + return createPrivateTransactionEnabledNode(name, privacyAccount, Address.PRIVACY); + } + + public PrivacyNode createPrivateTransactionEnabledNode( + final String name, final PrivacyAccount privacyAccount, final int privacyAddress) + throws IOException { return create( new PrivacyNodeConfiguration( + privacyAddress, new BesuNodeConfigurationBuilder() .name(name) .jsonRpcEnabled() @@ -72,8 +87,15 @@ public class PrivacyNodeFactory { public PrivacyNode createIbft2NodePrivacyEnabled( final String name, final PrivacyAccount privacyAccount) throws IOException { + return createIbft2NodePrivacyEnabled(name, privacyAccount, Address.PRIVACY); + } + + public PrivacyNode createIbft2NodePrivacyEnabled( + final String name, final PrivacyAccount privacyAccount, final int privacyAddress) + throws IOException { return create( new PrivacyNodeConfiguration( + privacyAddress, new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java index 3459562b1..503fd7b50 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java @@ -69,6 +69,7 @@ public class PrivacyNode implements AutoCloseable { private final OrionTestHarness orion; private final BesuNode besu; private final Vertx vertx; + private final Integer privacyAddress; public PrivacyNode(final PrivacyNodeConfiguration privacyConfiguration, final Vertx vertx) throws IOException { @@ -78,6 +79,8 @@ public class PrivacyNode implements AutoCloseable { final BesuNodeConfiguration besuConfig = privacyConfiguration.getBesuConfig(); + privacyAddress = privacyConfiguration.getPrivacyAddress(); + this.besu = new BesuNode( besuConfig.getName(), @@ -167,6 +170,7 @@ public class PrivacyNode implements AutoCloseable { .setStorageProvider(createKeyValueStorageProvider(dataDir, dbDir)) .setPrivateKeyPath(KeyPairUtil.getDefaultKeyFile(besu.homeDirectory()).toPath()) .setEnclaveFactory(new EnclaveFactory(vertx)) + .setPrivacyAddress(privacyAddress) .build(); } catch (IOException e) { throw new RuntimeException(); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidOnChainPrivacyGroupCreated.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidOnChainPrivacyGroupCreated.java new file mode 100644 index 000000000..7340c4924 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidOnChainPrivacyGroupCreated.java @@ -0,0 +1,52 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; +import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; + +import java.util.List; + +import org.awaitility.Awaitility; +import org.web3j.utils.Base64String; + +public class ExpectValidOnChainPrivacyGroupCreated implements PrivateCondition { + + private final PrivacyTransactions transactions; + private final PrivacyRequestFactory.OnChainPrivacyGroup expected; + + public ExpectValidOnChainPrivacyGroupCreated( + final PrivacyTransactions transactions, + final PrivacyRequestFactory.OnChainPrivacyGroup expected) { + this.transactions = transactions; + this.expected = expected; + } + + @Override + public void verify(final PrivacyNode node) { + Awaitility.await() + .untilAsserted( + () -> { + final List groups = + node.execute( + transactions.findOnChainPrivacyGroup( + Base64String.unwrapList(expected.getMembers()))); + assertThat(groups).contains(expected); + }); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java index 52f2fa85b..687d6360c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java @@ -43,6 +43,8 @@ public class ExpectValidPrivateContractDeployedReceipt implements PrivateContrac assertThat(receipt.isPresent()).isTrue(); final TransactionReceipt transactionReceipt = receipt.get(); + assertThat(transactionReceipt.isStatusOK()).isTrue(); + // Contract transaction has no 'to' address or contract address assertThat(transactionReceipt.getTo()).isNull(); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java index 6e1dff6b0..85dd5887d 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java @@ -24,21 +24,34 @@ import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; public class ExpectValidPrivateTransactionReceipt implements PrivateCondition { private final PrivacyTransactions transactions; private final String transactionHash; - private final PrivateTransactionReceipt receipt; + private final PrivateTransactionReceipt expectedReceipt; public ExpectValidPrivateTransactionReceipt( final PrivacyTransactions transactions, final String transactionHash, - final PrivateTransactionReceipt receipt) { + final PrivateTransactionReceipt expectedReceipt) { this.transactions = transactions; this.transactionHash = transactionHash; - this.receipt = receipt; + this.expectedReceipt = expectedReceipt; } @Override public void verify(final PrivacyNode node) { - assertThat(node.execute(transactions.getPrivateTransactionReceipt(transactionHash))) - .isEqualToIgnoringGivenFields(receipt, "commitmentHash"); + final PrivateTransactionReceipt actualReceipt = + node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); + assertThat(actualReceipt) + .usingRecursiveComparison() + .ignoringFields("commitmentHash", "logs") + .isEqualTo(expectedReceipt); + + assertThat(actualReceipt.getLogs().size()).isEqualTo(expectedReceipt.getLogs().size()); + + for (int i = 0; i < expectedReceipt.getLogs().size(); i++) { + assertThat(actualReceipt.getLogs().get(i)) + .usingRecursiveComparison() + .ignoringFields("blockHash", "blockNumber") + .isEqualTo(expectedReceipt.getLogs().get(i)); + } } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java index e1c0c0e5f..b5922b25b 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java @@ -39,7 +39,7 @@ public class PrivGetTransactionReceiptTransaction public PrivateTransactionReceipt execute(final NodeRequests node) { final Besu besu = node.privacy().getBesuClient(); final PollingPrivateTransactionReceiptProcessor receiptProcessor = - new PollingPrivateTransactionReceiptProcessor(besu, 15000, 3); + new PollingPrivateTransactionReceiptProcessor(besu, 1000, 15); try { final PrivateTransactionReceipt result = receiptProcessor.waitForTransactionReceipt(transactionHash); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java index 95f96c691..86473cb82 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; import org.web3j.protocol.besu.response.privacy.PrivacyGroup; import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; @@ -41,6 +42,11 @@ public class PrivateTransactionVerifier { return new ExpectValidPrivacyGroupCreated(transactions, expected); } + public ExpectValidOnChainPrivacyGroupCreated validOnChainPrivacyGroupExists( + final PrivacyRequestFactory.OnChainPrivacyGroup expected) { + return new ExpectValidOnChainPrivacyGroupCreated(transactions, expected); + } + public ExpectInternalErrorPrivateTransactionReceipt internalErrorPrivateTransactionReceipt( final String transactionHash) { return new ExpectInternalErrorPrivateTransactionReceipt(transactions, transactionHash); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnChianPermissioningPrivateSmartContractFunction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnChianPermissioningPrivateSmartContractFunction.java new file mode 100644 index 000000000..22bae51a7 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnChianPermissioningPrivateSmartContractFunction.java @@ -0,0 +1,78 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.math.BigInteger; + +import org.web3j.crypto.Credentials; +import org.web3j.protocol.besu.Besu; +import org.web3j.tx.BesuPrivateTransactionManager; +import org.web3j.tx.PrivateTransactionManager; +import org.web3j.tx.gas.BesuPrivacyGasProvider; +import org.web3j.utils.Base64String; + +public class CallOnChianPermissioningPrivateSmartContractFunction implements Transaction { + + private static final BesuPrivacyGasProvider GAS_PROVIDER = + new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); + private final String contractAddress; + private final String encodedFunction; + private final Credentials senderCredentials; + private final long chainId; + private final Base64String privateFrom; + private final Base64String privacyGroupId; + + public CallOnChianPermissioningPrivateSmartContractFunction( + final String contractAddress, + final String encodedFunction, + final String transactionSigningKey, + final long chainId, + final String privateFrom, + final String privacyGroupId) { + + this.contractAddress = contractAddress; + this.encodedFunction = encodedFunction; + this.senderCredentials = Credentials.create(transactionSigningKey); + this.chainId = chainId; + this.privateFrom = Base64String.wrap(privateFrom); + this.privacyGroupId = Base64String.wrap(privacyGroupId); + } + + @Override + public String execute(final NodeRequests node) { + final Besu besu = node.privacy().getBesuClient(); + + final PrivateTransactionManager privateTransactionManager = + new BesuPrivateTransactionManager( + besu, GAS_PROVIDER, senderCredentials, chainId, privateFrom, privacyGroupId); + + try { + return privateTransactionManager + .sendTransaction( + GAS_PROVIDER.getGasPrice(), + GAS_PROVIDER.getGasLimit(), + contractAddress, + encodedFunction, + null) + .getTransactionHash(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java index e7ee0f600..82df09537 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java @@ -106,4 +106,20 @@ public class PrivateContractTransactions { return new LoadPrivateSmartContractTransaction<>( contractAddress, clazz, transactionSigningKey, chainId, privateFrom, privateFor); } + + public CallOnChianPermissioningPrivateSmartContractFunction callOnChainPermissioningSmartContract( + final String contractAddress, + final String encodedFunction, + final String transactionSigningKey, + final long chainId, + final String privateFrom, + final String privacyGroupId) { + return new CallOnChianPermissioningPrivateSmartContractFunction( + contractAddress, + encodedFunction, + transactionSigningKey, + chainId, + privateFrom, + privacyGroupId); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToOnChainPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToOnChainPrivacyGroupTransaction.java new file mode 100644 index 000000000..58eed0670 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToOnChainPrivacyGroupTransaction.java @@ -0,0 +1,52 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; + +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.web3j.protocol.exceptions.TransactionException; +import org.web3j.utils.Base64String; + +public class AddToOnChainPrivacyGroupTransaction implements Transaction { + private final Base64String privacyGroupId; + private final PrivacyNode adder; + private final List addresses; + + public AddToOnChainPrivacyGroupTransaction( + final String privacyGroupId, final PrivacyNode adder, final PrivacyNode... nodes) { + this.privacyGroupId = Base64String.wrap(privacyGroupId); + this.adder = adder; + this.addresses = + Arrays.stream(nodes) + .map(n -> n.getOrion().getDefaultPublicKey()) + .collect(Collectors.toList()); + } + + @Override + public String execute(final NodeRequests node) { + try { + return node.privacy().privxAddToPrivacyGroup(privacyGroupId, adder, addresses); + } catch (IOException | TransactionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateOnChainPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateOnChainPrivacyGroupTransaction.java new file mode 100644 index 000000000..67cfb2f7a --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateOnChainPrivacyGroupTransaction.java @@ -0,0 +1,49 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; + +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CreateOnChainPrivacyGroupTransaction + implements Transaction { + private final PrivacyNode creator; + private final List addresses; + + public CreateOnChainPrivacyGroupTransaction( + final PrivacyNode creator, final PrivacyNode... nodes) { + this.creator = creator; + this.addresses = + Arrays.stream(nodes) + .map(n -> n.getOrion().getDefaultPublicKey()) + .collect(Collectors.toList()); + } + + @Override + public PrivxCreatePrivacyGroupResponse execute(final NodeRequests node) { + try { + return node.privacy().privxCreatePrivacyGroup(creator, addresses); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindOnChainPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindOnChainPrivacyGroupTransaction.java new file mode 100644 index 000000000..9100c5869 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindOnChainPrivacyGroupTransaction.java @@ -0,0 +1,43 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import org.web3j.utils.Base64String; + +public class FindOnChainPrivacyGroupTransaction + implements Transaction> { + private final List nodes; + + public FindOnChainPrivacyGroupTransaction(final List nodeEnclaveKeys) { + this.nodes = nodeEnclaveKeys.stream().map(Base64String::wrap).collect(Collectors.toList()); + } + + @Override + public List execute(final NodeRequests node) { + try { + return node.privacy().privxFindOnChainPrivacyGroup(nodes).send().getGroups(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockOnChainPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockOnChainPrivacyGroupTransaction.java new file mode 100644 index 000000000..5604cdd42 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockOnChainPrivacyGroupTransaction.java @@ -0,0 +1,43 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; + +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +import org.web3j.protocol.exceptions.TransactionException; +import org.web3j.utils.Base64String; + +public class LockOnChainPrivacyGroupTransaction implements Transaction { + private final Base64String privacyGroupId; + private final PrivacyNode locker; + + public LockOnChainPrivacyGroupTransaction(final String privacyGroupId, final PrivacyNode locker) { + this.privacyGroupId = Base64String.wrap(privacyGroupId); + this.locker = locker; + } + + @Override + public String execute(final NodeRequests node) { + try { + return node.privacy().privxLockPrivacyGroup(locker, privacyGroupId); + } catch (IOException | TransactionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java index e22cab717..4b2a18755 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java @@ -33,10 +33,29 @@ public class PrivacyTransactions { return new CreatePrivacyGroupTransaction(name, description, nodes); } + public CreateOnChainPrivacyGroupTransaction createOnChainPrivacyGroup( + final PrivacyNode creator, final PrivacyNode... nodes) { + return new CreateOnChainPrivacyGroupTransaction(creator, nodes); + } + + public AddToOnChainPrivacyGroupTransaction addToPrivacyGroup( + final String privacyGroupId, final PrivacyNode adder, final PrivacyNode... nodes) { + return new AddToOnChainPrivacyGroupTransaction(privacyGroupId, adder, nodes); + } + + public LockOnChainPrivacyGroupTransaction privxLockPrivacyGroup( + final String privacyGroupId, final PrivacyNode locker) { + return new LockOnChainPrivacyGroupTransaction(privacyGroupId, locker); + } + public FindPrivacyGroupTransaction findPrivacyGroup(final List nodes) { return new FindPrivacyGroupTransaction(nodes); } + public FindOnChainPrivacyGroupTransaction findOnChainPrivacyGroup(final List nodes) { + return new FindOnChainPrivacyGroupTransaction(nodes); + } + public PrivDistributeTransactionTransaction privDistributeTransaction( final String signedPrivateTransaction) { return new PrivDistributeTransactionTransaction(signedPrivateTransaction); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java index f844b006f..14bc9a550 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java @@ -18,6 +18,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; import java.lang.reflect.Method; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; @@ -33,28 +36,70 @@ public class DeploySmartContractTransaction implements Trans Credentials.create(Accounts.GENESIS_ACCOUNT_ONE_PRIVATE_KEY); private final Class clazz; + private final Object[] args; - public DeploySmartContractTransaction(final Class clazz) { + public DeploySmartContractTransaction(final Class clazz, final Object... args) { this.clazz = clazz; + this.args = args; } @Override public T execute(final NodeRequests node) { try { - final Method method = - clazz.getMethod( - "deploy", Web3j.class, Credentials.class, BigInteger.class, BigInteger.class); + if (args != null && args.length != 0) { + final ArrayList parameterObjects = new ArrayList<>(); + parameterObjects.addAll( + Arrays.asList(node.eth(), BENEFACTOR_ONE, DEFAULT_GAS_PRICE, DEFAULT_GAS_LIMIT)); + parameterObjects.addAll(Arrays.asList(args)); - final Object invoked = - method.invoke( - METHOD_IS_STATIC, node.eth(), BENEFACTOR_ONE, DEFAULT_GAS_PRICE, DEFAULT_GAS_LIMIT); + final Method method = + Arrays.stream(clazz.getMethods()) + .filter( + i -> + i.getName().equals("deploy") + && parameterTypesAreEqual(i.getParameterTypes(), parameterObjects)) + .findAny() + .orElseThrow(); + + final Object invoked = method.invoke(METHOD_IS_STATIC, parameterObjects.toArray()); + + return cast(invoked).send(); + } else { + final Method method = + clazz.getMethod( + "deploy", Web3j.class, Credentials.class, BigInteger.class, BigInteger.class); + + final Object invoked = + method.invoke( + METHOD_IS_STATIC, node.eth(), BENEFACTOR_ONE, DEFAULT_GAS_PRICE, DEFAULT_GAS_LIMIT); + + return cast(invoked).send(); + } - return cast(invoked).send(); } catch (final Exception e) { throw new RuntimeException(e); } } + @SuppressWarnings("rawtypes") + private boolean parameterTypesAreEqual( + final Class[] expectedTypes, final ArrayList actualObjects) { + if (expectedTypes.length != actualObjects.size()) { + return false; + } + final ArrayList actualTypes = + actualObjects.stream() + .map(Object::getClass) + .collect(Collectors.toCollection(ArrayList::new)); + + for (int i = 0; i < expectedTypes.length; i++) { + if (!expectedTypes[i].isAssignableFrom(actualTypes.get(i))) { + return false; + } + } + return true; + } + @SuppressWarnings("unchecked") private RemoteCall cast(final Object invokedMethod) { return (RemoteCall) invokedMethod; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/contract/ContractTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/contract/ContractTransactions.java index cdcebabbc..360db783e 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/contract/ContractTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/contract/ContractTransactions.java @@ -26,6 +26,11 @@ public class ContractTransactions { return new DeploySmartContractTransaction<>(clazz); } + public DeploySmartContractTransaction createSmartContract( + final Class clazz, final Object... args) { + return new DeploySmartContractTransaction<>(clazz, args); + } + public CallSmartContractFunction callSmartContract( final String functionName, final String contractAddress) { return new CallSmartContractFunction(functionName, contractAddress); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthFilterChangesTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthFilterChangesTransaction.java new file mode 100644 index 000000000..29c1e30a9 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthFilterChangesTransaction.java @@ -0,0 +1,44 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.math.BigInteger; + +import org.web3j.protocol.core.methods.response.EthLog; + +public class EthFilterChangesTransaction implements Transaction { + private final BigInteger filterId; + + public EthFilterChangesTransaction(final BigInteger filterId) { + this.filterId = filterId; + } + + @Override + public EthLog execute(final NodeRequests node) { + try { + final EthLog response = node.eth().ethGetFilterChanges(filterId).send(); + assertThat(response.getLogs()).isNotNull(); + return response; + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthNewPendingTransactionFilterTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthNewPendingTransactionFilterTransaction.java new file mode 100644 index 000000000..97130a2e2 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthNewPendingTransactionFilterTransaction.java @@ -0,0 +1,38 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.math.BigInteger; + +import org.web3j.protocol.core.methods.response.EthFilter; + +public class EthNewPendingTransactionFilterTransaction implements Transaction { + @Override + public BigInteger execute(final NodeRequests node) { + try { + final EthFilter response = node.eth().ethNewPendingTransactionFilter().send(); + assertThat(response.getFilterId()).isNotNull(); + return response.getFilterId(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java index 7b0ec6617..7fed1e13c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java @@ -16,6 +16,8 @@ package org.hyperledger.besu.tests.acceptance.dsl.transaction.eth; import org.hyperledger.besu.tests.acceptance.dsl.account.Account; +import java.math.BigInteger; + import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.DefaultBlockParameterName; @@ -65,4 +67,12 @@ public class EthTransactions { public EthMiningTransaction mining() { return new EthMiningTransaction(); } + + public EthNewPendingTransactionFilterTransaction newPendingTransactionsFilter() { + return new EthNewPendingTransactionFilterTransaction(); + } + + public EthFilterChangesTransaction filterChanges(final BigInteger filterId) { + return new EthFilterChangesTransaction(filterId); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java index 8dd6330a9..214d29611 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java @@ -14,26 +14,44 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; +import static com.google.common.base.Preconditions.checkArgument; import static java.util.Collections.singletonList; +import org.hyperledger.besu.crypto.SecureRandomProvider; import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.parameters.CreatePrivacyGroupParameter; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement; +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivateTransactionGroupResponse; +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes; +import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3jService; import org.web3j.protocol.besu.Besu; import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; import org.web3j.protocol.core.Request; import org.web3j.protocol.core.Response; +import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder; +import org.web3j.protocol.eea.crypto.RawPrivateTransaction; +import org.web3j.protocol.exceptions.TransactionException; +import org.web3j.tx.response.PollingPrivateTransactionReceiptProcessor; +import org.web3j.utils.Base64String; +import org.web3j.utils.Numeric; public class PrivacyRequestFactory { + private final SecureRandom secureRandom; public static class GetPrivacyPrecompileAddressResponse extends Response
{} @@ -81,6 +99,7 @@ public class PrivacyRequestFactory { public PrivacyRequestFactory(final Web3jService web3jService) { this.web3jService = web3jService; this.besuClient = Besu.build(web3jService); + this.secureRandom = SecureRandomProvider.createSecureRandom(); } public Besu getBesuClient() { @@ -96,6 +115,126 @@ public class PrivacyRequestFactory { } } + public String privxAddToPrivacyGroup( + final Base64String privacyGroupId, final PrivacyNode adder, final List addresses) + throws IOException, TransactionException { + + final BigInteger nonce = + besuClient + .privGetTransactionCount(adder.getAddress().toHexString(), privacyGroupId) + .send() + .getTransactionCount(); + + final Bytes payload = + encodeAddToGroupFunctionCall( + Bytes.fromBase64String(adder.getEnclaveKey()), + addresses.stream().map(Bytes::fromBase64String).collect(Collectors.toList())); + + final RawPrivateTransaction privateTransaction = + RawPrivateTransaction.createTransaction( + nonce, + BigInteger.valueOf(1000), + BigInteger.valueOf(3000000), + Address.PRIVACY_PROXY.toHexString(), + payload.toHexString(), + Base64String.wrap(adder.getEnclaveKey()), + privacyGroupId, + org.web3j.utils.Restriction.RESTRICTED); + + return besuClient + .eeaSendRawTransaction( + Numeric.toHexString( + PrivateTransactionEncoder.signMessage( + privateTransaction, Credentials.create(adder.getTransactionSigningKey())))) + .send() + .getTransactionHash(); + } + + public String privxLockPrivacyGroup(final PrivacyNode locker, final Base64String privacyGroupId) + throws IOException, TransactionException { + final BigInteger nonce = + besuClient + .privGetTransactionCount(locker.getAddress().toHexString(), privacyGroupId) + .send() + .getTransactionCount(); + + final RawPrivateTransaction privateTransaction = + RawPrivateTransaction.createTransaction( + nonce, + BigInteger.valueOf(1000), + BigInteger.valueOf(3000000), + Address.PRIVACY_PROXY.toHexString(), + OnChainGroupManagement.LOCK_GROUP_METHOD_SIGNATURE.toHexString(), + Base64String.wrap(locker.getEnclaveKey()), + privacyGroupId, + org.web3j.utils.Restriction.RESTRICTED); + + final String transactionHash = + besuClient + .eeaSendRawTransaction( + Numeric.toHexString( + PrivateTransactionEncoder.signMessage( + privateTransaction, Credentials.create(locker.getTransactionSigningKey())))) + .send() + .getTransactionHash(); + + return new PollingPrivateTransactionReceiptProcessor(besuClient, 3000, 10) + .waitForTransactionReceipt(transactionHash) + .getcommitmentHash(); + } + + public PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroup( + final PrivacyNode creator, final List addresses) throws IOException { + + final byte[] bytes = new byte[32]; + 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.valueOf(1000), + BigInteger.valueOf(3000000), + Address.PRIVACY_PROXY.toHexString(), + payload.toHexString(), + Base64String.wrap(creator.getEnclaveKey()), + 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(); + return new PrivxCreatePrivacyGroupResponse(privacyGroupId.toBase64String(), transactionHash); + } + + public Request privxFindOnChainPrivacyGroup( + final List nodes) { + return new Request<>( + "privx_findOnChainPrivacyGroup", + singletonList(nodes), + web3jService, + PrivxFindPrivacyGroupResponse.class); + } + public Request privGetPrivacyPrecompileAddress() { return new Request<>( "priv_getPrivacyPrecompileAddress", @@ -179,4 +318,131 @@ public class PrivacyRequestFactory { web3jService, GetCodeResponse.class); } + + public static class PrivxFindPrivacyGroupResponse extends Response> { + public List getGroups() { + return getResult(); + } + } + + public static class OnChainPrivacyGroup { + private final Base64String privacyGroupId; + private final List members; + private final String name; + private final String description; + + public enum Type { + ONCHAIN + } + + @JsonCreator + public OnChainPrivacyGroup( + @JsonProperty(value = "privacyGroupId") final String privacyGroupId, + @JsonProperty(value = "type") final Type type, + @JsonProperty(value = "name") final String name, + @JsonProperty(value = "description") final String description, + @JsonProperty(value = "members") final List members) { + this(privacyGroupId, members); + } + + public OnChainPrivacyGroup(final String privacyGroupId, final List members) { + this.privacyGroupId = Base64String.wrap(privacyGroupId); + this.name = ""; + this.description = ""; + this.members = members; + } + + public Base64String getPrivacyGroupId() { + return privacyGroupId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Type getType() { + return Type.ONCHAIN; + } + + public List getMembers() { + return members; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final OnChainPrivacyGroup that = (OnChainPrivacyGroup) o; + return getPrivacyGroupId().equals(that.getPrivacyGroupId()) + && getName().equals(that.getName()) + && getDescription().equals(that.getDescription()) + && getType() == that.getType() + && getMembers().equals(that.getMembers()); + } + + @Override + public int hashCode() { + return Objects.hash( + getPrivacyGroupId(), getName(), getDescription(), getType(), getMembers()); + } + } + + public static class PrivxCreatePrivacyGroupResponse { + final String privacyGroupId; + final String transactionHash; + + @JsonCreator + public PrivxCreatePrivacyGroupResponse( + @JsonProperty("privacyGroupId") final String privacyGroupId, + @JsonProperty("transactionHash") final String transactionHash) { + this.privacyGroupId = privacyGroupId; + this.transactionHash = transactionHash; + } + + public String getPrivacyGroupId() { + return privacyGroupId; + } + + public String getTransactionHash() { + return transactionHash; + } + } + + private Bytes encodeAddToGroupFunctionCall( + final Bytes privateFrom, final List participants) { + return Bytes.concatenate( + OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE, + privateFrom, + encodeList(participants)); + } + + private Bytes encodeList(final List participants) { + final Bytes dynamicParameterOffset = encodeLong(64); + final Bytes length = encodeLong(participants.size()); + return Bytes.concatenate( + dynamicParameterOffset, + length, + Bytes.fromHexString( + participants.stream() + .map(Bytes::toUnprefixedHexString) + .collect(Collectors.joining("")))); + } + + // long to uint256, 8 bytes big endian, so left padded by 24 bytes + private static Bytes encodeLong(final long l) { + checkArgument(l >= 0, "Unsigned value must be positive"); + final byte[] longBytes = new byte[8]; + for (int i = 0; i < 8; i++) { + longBytes[i] = (byte) ((l >> ((7 - i) * 8)) & 0xFF); + } + return Bytes.concatenate(Bytes.wrap(new byte[24]), Bytes.wrap(longBytes)); + } } diff --git a/acceptance-tests/tests/build.gradle b/acceptance-tests/tests/build.gradle index 76aa16463..ad38ee6a6 100644 --- a/acceptance-tests/tests/build.gradle +++ b/acceptance-tests/tests/build.gradle @@ -20,6 +20,7 @@ dependencies { testImplementation project(':ethereum:api') testImplementation project(':ethereum:core') testImplementation project(':ethereum:rlp') + testImplementation project(':privacy-contracts') testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') testImplementation project(':ethereum:permissioning') testImplementation project(':plugin-api') diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java index 1971fbab6..e2620ff0d 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java @@ -20,6 +20,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.hyperledger.besu.ethereum.core.Address.DEFAULT_PRIVACY; import org.hyperledger.besu.crypto.SECP256K1; @@ -56,6 +57,11 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { private final ObjectMapper mapper = new ObjectMapper(); private Cluster multiTenancyCluster; + private static final SECP256K1.KeyPair TEST_KEY = + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", 16))); private static final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private static final String ENCLAVE_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private static final String KEY1 = "sgFkVOyFndZe/5SAZJO5UYbrl7pezHetveriBBWWnE8="; @@ -99,9 +105,9 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { final PrivateTransaction validSignedPrivateTransaction = getValidSignedPrivateTransaction(senderAddress); - receiveEnclaveStub(getRLPOutput(validSignedPrivateTransaction)); + receiveEnclaveStub(validSignedPrivateTransaction); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub("testKey"); + sendEnclaveStub(KEY1); final Hash transactionHash = node.execute( @@ -133,9 +139,9 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { throws JsonProcessingException { final List groupMembership = List.of( - testPrivacyGroup(emptyList(), PrivacyGroup.Type.PANTHEON), - testPrivacyGroup(emptyList(), PrivacyGroup.Type.PANTHEON), - testPrivacyGroup(emptyList(), PrivacyGroup.Type.PANTHEON)); + testPrivacyGroup(singletonList(ENCLAVE_KEY), PrivacyGroup.Type.PANTHEON), + testPrivacyGroup(singletonList(ENCLAVE_KEY), PrivacyGroup.Type.PANTHEON), + testPrivacyGroup(singletonList(ENCLAVE_KEY), PrivacyGroup.Type.PANTHEON)); findPrivacyGroupEnclaveStub(groupMembership); @@ -149,8 +155,8 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { getValidSignedPrivateTransaction(senderAddress); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub("testKey"); - receiveEnclaveStub(getRLPOutput(validSignedPrivateTransaction)); + sendEnclaveStub(KEY1); + receiveEnclaveStub(validSignedPrivateTransaction); node.verify( priv.eeaSendRawTransaction( @@ -166,8 +172,8 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub("testKey"); - receiveEnclaveStub(rlpOutput); + sendEnclaveStub(KEY1); + receiveEnclaveStub(validSignedPrivateTransaction); node.verify(priv.getTransactionCount(accountAddress, PRIVACY_GROUP_ID, 0)); final Hash transactionReceipt = @@ -180,14 +186,10 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { @Test public void privDistributeRawTransactionSuccessShouldReturnEnclaveKey() throws JsonProcessingException { - final String enclaveResponseKey = "TestKey"; - final String enclaveResponseKeyBase64 = - Base64.encode(Bytes.wrap(enclaveResponseKey.getBytes(UTF_8))); - final String enclaveResponseKeyBytes = - Bytes.wrap(Bytes.fromBase64String(enclaveResponseKeyBase64)).toString(); + final String enclaveResponseKeyBytes = Bytes.wrap(Bytes.fromBase64String(KEY1)).toString(); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(enclaveResponseKeyBase64); + sendEnclaveStub(KEY1); node.verify( priv.distributeRawTransaction( @@ -203,8 +205,8 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub("testKey"); - receiveEnclaveStub(rlpOutput); + sendEnclaveStub(KEY1); + receiveEnclaveStub(validSignedPrivateTransaction); final Hash transactionReceipt = node.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); @@ -224,8 +226,8 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { List.of(testPrivacyGroup(emptyList(), PrivacyGroup.Type.LEGACY)); retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub("testKey"); - receiveEnclaveStub(rlpOutput); + sendEnclaveStub(KEY1); + receiveEnclaveStub(validSignedPrivateTransaction); findPrivacyGroupEnclaveStub(groupMembership); node.verify(priv.getTransactionCount(accountAddress, PRIVACY_GROUP_ID, 0)); @@ -268,8 +270,8 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { stubFor(post("/send").willReturn(ok(sendResponse))); } - private void receiveEnclaveStub(final BytesValueRLPOutput rlpOutput) - throws JsonProcessingException { + private void receiveEnclaveStub(final PrivateTransaction privTx) throws JsonProcessingException { + final BytesValueRLPOutput rlpOutput = getRLPOutputForReceiveResponse(privTx); final String senderKey = "QTFhVnRNeExDVUhtQlZIWG9aenpCZ1BiVy93ajVheERwVzlYOGw5MVNHbz0="; final String receiveResponse = mapper.writeValueAsString( @@ -278,9 +280,16 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { stubFor(post("/receive").willReturn(ok(receiveResponse))); } - private BytesValueRLPOutput getRLPOutput(final PrivateTransaction validSignedPrivateTransaction) { + private BytesValueRLPOutput getRLPOutputForReceiveResponse( + final PrivateTransaction privateTransaction) { final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - validSignedPrivateTransaction.writeTo(bvrlpo); + privateTransaction.writeTo(bvrlpo); + return bvrlpo; + } + + private BytesValueRLPOutput getRLPOutput(final PrivateTransaction privateTransaction) { + final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); + privateTransaction.writeTo(bvrlpo); return bvrlpo; } @@ -302,10 +311,6 @@ public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { .privateFrom(Bytes.fromBase64String(ENCLAVE_KEY)) .restriction(Restriction.RESTRICTED) .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) - .signAndBuild( - SECP256K1.KeyPair.create( - SECP256K1.PrivateKey.create( - new BigInteger( - "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", 16)))); + .signAndBuild(TEST_KEY); } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/OnChainPrivacyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/OnChainPrivacyAcceptanceTest.java new file mode 100644 index 000000000..4062edd4d --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/OnChainPrivacyAcceptanceTest.java @@ -0,0 +1,403 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.web3j.privacy; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.tests.acceptance.dsl.condition.eth.EthConditions; +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.OnChainPrivacyGroup; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse; +import org.hyperledger.besu.tests.web3j.generated.EventEmitter; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.Before; +import org.junit.Test; +import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.utils.Base64String; + +public class OnChainPrivacyAcceptanceTest extends PrivacyAcceptanceTestBase { + private static final long POW_CHAIN_ID = 2018; + + private PrivacyNode alice; + private PrivacyNode bob; + private PrivacyNode charlie; + + private final MinerTransactions minerTransactions = new MinerTransactions(); + private final EthConditions ethConditions = new EthConditions(ethTransactions); + + private static final String EXPECTED_STORE_OUTPUT_DATA = + "0x000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b7320000000000000000000000000000000000000000000000000000000000000539"; + private static final String EXPECTED_STORE_EVENT_TOPIC = + "0xc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5"; + + @Before + public void setUp() throws Exception { + alice = + privacyBesu.createPrivateTransactionEnabledMinerNode( + "node1", privacyAccountResolver.resolve(0), Address.PRIVACY); + bob = + privacyBesu.createPrivateTransactionEnabledNode( + "node2", privacyAccountResolver.resolve(1), Address.PRIVACY); + charlie = + privacyBesu.createPrivateTransactionEnabledNode( + "node3", privacyAccountResolver.resolve(2), Address.PRIVACY); + privacyCluster.start(alice, bob, charlie); + } + + @Test + public void nodeCanCreatePrivacyGroup() { + final PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroupResponse = + alice.execute(privacyTransactions.createOnChainPrivacyGroup(alice, alice, bob)); + + assertThat(privxCreatePrivacyGroupResponse).isNotNull(); + + final OnChainPrivacyGroup expectedGroup = + new OnChainPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); + + alice.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + bob.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + final String getParticipantsCallHash = + alice.execute( + privateContractTransactions.callOnChainPermissioningSmartContract( + Address.PRIVACY_PROXY.toHexString(), + "0x0b0235be" // get participants method signature + + Bytes.fromBase64String(alice.getEnclaveKey()).toUnprefixedHexString(), + alice.getTransactionSigningKey(), + POW_CHAIN_ID, + alice.getEnclaveKey(), + privxCreatePrivacyGroupResponse.getPrivacyGroupId())); + + final PrivateTransactionReceipt expectedReceipt = + new PrivateTransactionReceipt( + null, + alice.getAddress().toHexString(), + Address.PRIVACY_PROXY.toHexString(), + "0x0000000000000000000000000000000000000000000000000000000000000020" // dynamic + // array offset + + "0000000000000000000000000000000000000000000000000000000000000002" // length + // of array + + Bytes.fromBase64String(alice.getEnclaveKey()).toUnprefixedHexString() // first + // element + + Bytes.fromBase64String(bob.getEnclaveKey()).toUnprefixedHexString(), // second + // element + Collections.emptyList(), + null, + null, + alice.getEnclaveKey(), + null, + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + "0x1", + null); + + alice.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + getParticipantsCallHash, expectedReceipt)); + + bob.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + getParticipantsCallHash, expectedReceipt)); + } + + @Test + public void deployingMustGiveValidReceipt() { + final PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroupResponse = + alice.execute(privacyTransactions.createOnChainPrivacyGroup(alice)); + + final OnChainPrivacyGroup expectedGroup = + new OnChainPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + Base64String.wrapList(alice.getEnclaveKey())); + + alice.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + final EventEmitter eventEmitter = + alice.execute( + privateContractTransactions.createSmartContractWithPrivacyGroupId( + EventEmitter.class, + alice.getTransactionSigningKey(), + POW_CHAIN_ID, + alice.getEnclaveKey(), + privxCreatePrivacyGroupResponse.getPrivacyGroupId())); + + privateContractVerifier + .validPrivateContractDeployed( + eventEmitter.getContractAddress(), alice.getAddress().toString()) + .verify(eventEmitter); + } + + @Test + public void canAddParticipantToGroup() { + final PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroupResponse = + alice.execute(privacyTransactions.createOnChainPrivacyGroup(alice, alice, bob)); + + assertThat(privxCreatePrivacyGroupResponse).isNotNull(); + + final OnChainPrivacyGroup expectedGroup = + new OnChainPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); + + alice.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + bob.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + final EventEmitter eventEmitter = + alice.execute( + privateContractTransactions.createSmartContractWithPrivacyGroupId( + EventEmitter.class, + alice.getTransactionSigningKey(), + POW_CHAIN_ID, + alice.getEnclaveKey(), + privxCreatePrivacyGroupResponse.getPrivacyGroupId())); + + privateContractVerifier + .validPrivateContractDeployed( + eventEmitter.getContractAddress(), alice.getAddress().toString()) + .verify(eventEmitter); + + alice.execute( + privacyTransactions.privxLockPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), alice)); + + alice.execute( + privacyTransactions.addToPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), alice, charlie)); + + final OnChainPrivacyGroup expectedGroupAfterCharlieIsAdded = + new OnChainPrivacyGroup( + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + Base64String.wrapList( + alice.getEnclaveKey(), bob.getEnclaveKey(), charlie.getEnclaveKey())); + + alice.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + + bob.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + + charlie.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + } + + @Test + public void bobCanAddCharlieAfterBeingAddedByAlice() { + final PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroupResponse = + alice.execute(privacyTransactions.createOnChainPrivacyGroup(alice, alice)); + + assertThat(privxCreatePrivacyGroupResponse).isNotNull(); + + final String privacyGroupId = privxCreatePrivacyGroupResponse.getPrivacyGroupId(); + final OnChainPrivacyGroup expectedGroup = + new OnChainPrivacyGroup(privacyGroupId, Base64String.wrapList(alice.getEnclaveKey())); + + alice.verify(privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroup)); + + final EventEmitter eventEmitter = + alice.execute( + privateContractTransactions.createSmartContractWithPrivacyGroupId( + EventEmitter.class, + alice.getTransactionSigningKey(), + POW_CHAIN_ID, + alice.getEnclaveKey(), + privacyGroupId)); + privateContractVerifier + .validPrivateContractDeployed( + eventEmitter.getContractAddress(), alice.getAddress().toString()) + .verify(eventEmitter); + + final String aliceLockHash = + alice.execute(privacyTransactions.privxLockPrivacyGroup(privacyGroupId, alice)); + + alice.execute(privacyTransactions.addToPrivacyGroup(privacyGroupId, alice, bob)); + + final OnChainPrivacyGroup expectedGroupAfterBobIsAdded = + new OnChainPrivacyGroup( + privacyGroupId, Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); + + alice.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroupAfterBobIsAdded)); + + bob.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists(expectedGroupAfterBobIsAdded)); + + bob.execute(privacyTransactions.privxLockPrivacyGroup(privacyGroupId, bob)); + + alice.execute(minerTransactions.minerStop()); + + alice.getBesu().verify(ethConditions.miningStatus(false)); + + final BigInteger pendingTransactionFilterId = + alice.execute(ethTransactions.newPendingTransactionsFilter()); + + final String callHash = + alice.execute( + privateContractTransactions.callOnChainPermissioningSmartContract( + eventEmitter.getContractAddress(), + eventEmitter.value().encodeFunctionCall(), + alice.getTransactionSigningKey(), + POW_CHAIN_ID, + alice.getEnclaveKey(), + privacyGroupId)); + + final String bobAddHash = + bob.execute(privacyTransactions.addToPrivacyGroup(privacyGroupId, bob, charlie)); + + alice + .getBesu() + .verify( + ethConditions.expectNewPendingTransactions( + pendingTransactionFilterId, Arrays.asList(callHash, bobAddHash))); + + alice.execute(minerTransactions.minerStart()); + + alice.getBesu().verify(ethConditions.miningStatus(true)); + + final OnChainPrivacyGroup expectedGroupAfterCharlieIsAdded = + new OnChainPrivacyGroup( + privacyGroupId, + Base64String.wrapList( + alice.getEnclaveKey(), bob.getEnclaveKey(), charlie.getEnclaveKey())); + + alice.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + + bob.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + + charlie.verify( + privateTransactionVerifier.validOnChainPrivacyGroupExists( + expectedGroupAfterCharlieIsAdded)); + + final Optional aliceAddReceipt = + alice.execute(ethTransactions.getTransactionReceipt(bobAddHash)); + assertThat(aliceAddReceipt.get().getStatus()) + .isEqualTo("0x1"); // this means the PMT for the "add" succeeded which is what we expect + + final Optional alicePublicReceipt = + alice.execute(ethTransactions.getTransactionReceipt(callHash)); + if (alicePublicReceipt.isPresent()) { + assertThat(alicePublicReceipt.get().getBlockHash()) + .isEqualTo( + aliceAddReceipt + .get() + .getBlockHash()); // ensure that "add" and "call" are in the same block + assertThat(alicePublicReceipt.get().getStatus()) + .isEqualTo( + "0x1"); // this means the PMT for the "call" succeeded which is what we expect because + // it is in the same block as the "add" and there is no way to tell that this + // will happen before the block is mined + } + + final PrivateTransactionReceipt aliceReceipt = + alice.execute(privacyTransactions.getPrivateTransactionReceipt(callHash)); + assertThat(aliceReceipt.getStatus()) + .isEqualTo( + "0x0"); // this means the "call" failed which is what we expect because the group was + // locked! + final PrivateTransactionReceipt bobReceipt = + alice.execute(privacyTransactions.getPrivateTransactionReceipt(callHash)); + assertThat(bobReceipt.getStatus()) + .isEqualTo( + "0x0"); // this means the "call" failed which is what we expect because the group was + // locked! + + // assert charlie can access private transaction information from before he was added + final PrivateTransactionReceipt expectedAliceLockReceipt = + new PrivateTransactionReceipt( + null, + alice.getAddress().toHexString(), + Address.PRIVACY_PROXY.toHexString(), + "0x", + Collections.emptyList(), + null, + null, + alice.getEnclaveKey(), + null, + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + "0x1", + null); + charlie.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + aliceLockHash, expectedAliceLockReceipt)); + + final String aliceStoreHash = + charlie.execute( + privateContractTransactions.callOnChainPermissioningSmartContract( + eventEmitter.getContractAddress(), + eventEmitter.store(BigInteger.valueOf(1337)).encodeFunctionCall(), + charlie.getTransactionSigningKey(), + POW_CHAIN_ID, + charlie.getEnclaveKey(), + privacyGroupId)); + + final PrivateTransactionReceipt expectedStoreReceipt = + new PrivateTransactionReceipt( + null, + charlie.getAddress().toHexString(), + eventEmitter.getContractAddress(), + "0x", + Collections.singletonList( + new Log( + false, + "0x0", + "0x0", + aliceStoreHash, + null, + null, + eventEmitter.getContractAddress(), + EXPECTED_STORE_OUTPUT_DATA, + null, + Collections.singletonList(EXPECTED_STORE_EVENT_TOPIC))), + null, + null, + charlie.getEnclaveKey(), + null, + privxCreatePrivacyGroupResponse.getPrivacyGroupId(), + "0x1", + null); + + alice.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + aliceStoreHash, expectedStoreReceipt)); + + bob.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + aliceStoreHash, expectedStoreReceipt)); + + charlie.verify( + privateTransactionVerifier.validPrivateTransactionReceipt( + aliceStoreHash, expectedStoreReceipt)); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyGroupTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyGroupTest.java new file mode 100644 index 000000000..c83436904 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyGroupTest.java @@ -0,0 +1,143 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.web3j.privacy.contracts; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.privacy.contracts.generated.PrivacyGroup; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.web3j.protocol.exceptions.TransactionException; +import org.web3j.utils.Base64String; + +@SuppressWarnings("unchecked") +public class PrivacyGroupTest extends AcceptanceTestBase { + + private final Base64String firstParticipant = + Base64String.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + private final Base64String secondParticipant = + Base64String.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); + private final Base64String thirdParticipant = + Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); + private PrivacyGroup privacyGroup; + + private static final String RAW_FIRST_PARTICIPANT = + "0x0b0235be035695b4cc4b0941e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486a"; + private static final String RAW_ADD_PARTICIPANT = + "0xf744b089035695b4cc4b0941e60551d7a19cf30603db5bfc23e5ac43a56f57f25f75486a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000012a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b"; + private static final String RAW_LOCK = "0xf83d08ba"; + private static final String RAW_UNLOCK = "0xa69df4b5"; + private static final String RAW_CAN_EXECUTE = "0x78b90337"; + private static final String RAW_GET_VERSION = "0x0d8e6e2c"; + + private BesuNode minerNode; + + @Before + public void setUp() throws Exception { + minerNode = besu.createMinerNode("node"); + cluster.start(minerNode); + privacyGroup = minerNode.execute(contractTransactions.createSmartContract(PrivacyGroup.class)); + } + + @Test + public void rlp() throws Exception { + final String contractAddress = "0x42699a7612a82f1d9c36148af9c77354759b210b"; + assertThat(privacyGroup.isValid()).isEqualTo(true); + contractVerifier.validTransactionReceipt(contractAddress).verify(privacyGroup); + // 0x0b0235be + assertThat(RAW_FIRST_PARTICIPANT) + .isEqualTo(privacyGroup.getParticipants(firstParticipant.raw()).encodeFunctionCall()); + // 0xf744b089 + assertThat(RAW_ADD_PARTICIPANT) + .isEqualTo( + privacyGroup + .addParticipants( + firstParticipant.raw(), Collections.singletonList(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()); + } + + @Test + public void canInitiallyAddParticipants() throws Exception { + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final List participants = privacyGroup.getParticipants(firstParticipant.raw()).send(); + assertThat(participants.size()).isEqualTo(2); + assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); + assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); + } + + @Test(expected = TransactionException.class) + public void cannotAddToContractWhenNotLocked() throws Exception { + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw())) + .send(); + + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + } + + @Test + public void ensureContractIsNotLockedAfterDeploy() throws Exception { + privacyGroup.unlock().send(); + assertThat(privacyGroup.canExecute().send()).isTrue(); + } + + @Test + public void canAddTwiceToContractWhenCallLock() throws Exception { + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw())) + .send(); + privacyGroup.lock().send(); + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final BigInteger privacyGroupVersion = privacyGroup.getVersion().send(); + assertThat(privacyGroupVersion).isEqualTo(BigInteger.TWO); + + final List participants = privacyGroup.getParticipants(firstParticipant.raw()).send(); + assertThat(participants.size()).isEqualTo(3); + assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); + assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1)); + assertThat(secondParticipant.raw()).isEqualTo(participants.get(2)); + } + + @Test + public void versionStartsAtZero() throws Exception { + final BigInteger privacyGroupVersion = privacyGroup.getVersion().send(); + assertThat(privacyGroupVersion).isEqualTo(BigInteger.ZERO); + } + + @Test(expected = TransactionException.class) + public void cannotLockTwice() throws Exception { + privacyGroup + .addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw())) + .send(); + privacyGroup.lock().send(); + privacyGroup.lock().send(); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyProxyTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyProxyTest.java new file mode 100644 index 000000000..52561b276 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/contracts/PrivacyProxyTest.java @@ -0,0 +1,136 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.web3j.privacy.contracts; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.privacy.contracts.generated.PrivacyGroup; +import org.hyperledger.besu.privacy.contracts.generated.PrivacyProxy; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.web3j.utils.Base64String; + +@SuppressWarnings("unchecked") +public class PrivacyProxyTest extends AcceptanceTestBase { + + private final Base64String firstParticipant = + Base64String.wrap("93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="); + private final Base64String secondParticipant = + Base64String.wrap("9iaJ6OObl6TUWYjXAOyZsL0VaDPwF+tRFkMwwYSeqqw="); + private final Base64String thirdParticipant = + Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); + private PrivacyProxy privacyProxy; + + private static final String RAW_FIRST_PARTICIPANT = + "0x0b0235bef772b2ee55f016431cefe724a05814324bb96e9afdb73e338665a693d4653678"; + private static final String RAW_ADD_PARTICIPANT = + "0xf744b089f772b2ee55f016431cefe724a05814324bb96e9afdb73e338665a693d465367800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"; + + private BesuNode minerNode; + + @Before + public void setUp() throws Exception { + minerNode = besu.createMinerNode("node"); + cluster.start(minerNode); + PrivacyGroup privacyGroup = + minerNode.execute(contractTransactions.createSmartContract(PrivacyGroup.class)); + privacyProxy = + minerNode.execute( + contractTransactions.createSmartContract( + PrivacyProxy.class, privacyGroup.getContractAddress())); + } + + @Test + public void rlp() throws Exception { + assertThat(privacyProxy.isValid()).isEqualTo(true); + contractVerifier + .validTransactionReceipt(privacyProxy.getContractAddress()) + .verify(privacyProxy); + // 0x0b0235be + assertThat(RAW_FIRST_PARTICIPANT) + .isEqualTo(privacyProxy.getParticipants(firstParticipant.raw()).encodeFunctionCall()); + // 0xf744b089 + assertThat(RAW_ADD_PARTICIPANT) + .isEqualTo( + privacyProxy + .addParticipants(firstParticipant.raw(), 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 participants = privacyProxy.getParticipants(firstParticipant.raw()).send(); + assertThat(participants.size()).isEqualTo(0); + } + + @Test + public void canAddParticipants() throws Exception { + privacyProxy + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final List participants = privacyProxy.getParticipants(firstParticipant.raw()).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 { + privacyProxy + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final List participants = privacyProxy.getParticipants(firstParticipant.raw()).send(); + assertThat(participants.size()).isEqualTo(2); + assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); + assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); + + final PrivacyGroup upgradedContract = + minerNode.execute(contractTransactions.createSmartContract(PrivacyGroup.class)); + + privacyProxy.upgradeTo(upgradedContract.getContractAddress()).send(); + privacyProxy + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final List participantsAfterUpgrade = + privacyProxy.getParticipants(firstParticipant.raw()).send(); + assertThat(participantsAfterUpgrade.size()).isEqualTo(2); + assertThat(firstParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(0)); + assertThat(secondParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(1)); + } + + @Test + public void canAddTwiceToContractWhenCallLock() throws Exception { + privacyProxy + .addParticipants(firstParticipant.raw(), Collections.singletonList(thirdParticipant.raw())) + .send(); + privacyProxy.lock().send(); + privacyProxy + .addParticipants(firstParticipant.raw(), Collections.singletonList(secondParticipant.raw())) + .send(); + final List participants = privacyProxy.getParticipants(firstParticipant.raw()).send(); + assertThat(participants.size()).isEqualTo(3); + assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); + assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1)); + assertThat(secondParticipant.raw()).isEqualTo(participants.get(2)); + } +} diff --git a/acceptance-tests/tests/src/test/resources/log4j2.xml b/acceptance-tests/tests/src/test/resources/log4j2.xml index fedada36d..3ae674bfe 100644 --- a/acceptance-tests/tests/src/test/resources/log4j2.xml +++ b/acceptance-tests/tests/src/test/resources/log4j2.xml @@ -15,6 +15,8 @@ + + diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 23d2a5e68..80945fe4e 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -219,6 +219,9 @@ public abstract class BesuControllerBuilder { this::createConsensusContext); validateContext(protocolContext); + protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( + protocolContext.getWorldStateArchive()); + final MutableBlockchain blockchain = protocolContext.getBlockchain(); Optional maybePruner = Optional.empty(); diff --git a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java index 75356283c..aa6e41aca 100644 --- a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java +++ b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java @@ -82,6 +82,7 @@ public class PrivacyGroup implements Serializable { public enum Type { LEGACY, + ONCHAIN, PANTHEON } } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivGetPrivateTransactionIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivGetPrivateTransactionIntegrationTest.java index 37289c7fb..cd0bfceec 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivGetPrivateTransactionIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivGetPrivateTransactionIntegrationTest.java @@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.privacy.DefaultPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.Restriction; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.orion.testutil.OrionKeyConfiguration; import org.hyperledger.orion.testutil.OrionTestHarness; @@ -56,7 +57,6 @@ import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -74,13 +74,14 @@ public class PrivGetPrivateTransactionIntegrationTest { private final TransactionWithMetadata returnedTransaction = mock(TransactionWithMetadata.class); private final Transaction justTransaction = mock(Transaction.class); + private final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class); private static final Vertx vertx = Vertx.vertx(); private final EnclavePublicKeyProvider enclavePublicKeyProvider = (user) -> ENCLAVE_PUBLIC_KEY; - @BeforeClass - public static void setUpOnce() throws Exception { + @Before + public void setUp() throws Exception { folder.create(); testHarness = @@ -92,7 +93,9 @@ public class PrivGetPrivateTransactionIntegrationTest { final EnclaveFactory factory = new EnclaveFactory(vertx); enclave = factory.createVertxEnclave(testHarness.clientUrl()); - privacyController = new DefaultPrivacyController(enclave, null, null, null, null, null); + privacyController = + new DefaultPrivacyController( + null, privateStateStorage, enclave, null, null, null, null, null); } @AfterClass @@ -151,11 +154,13 @@ public class PrivGetPrivateTransactionIntegrationTest { public void returnsStoredPrivateTransaction() { final PrivGetPrivateTransaction privGetPrivateTransaction = - new PrivGetPrivateTransaction(blockchain, privacyController, enclavePublicKeyProvider); + new PrivGetPrivateTransaction( + blockchain, privacyController, privateStateStorage, enclavePublicKeyProvider); when(blockchain.transactionByHash(any(Hash.class))) .thenReturn(Optional.of(returnedTransaction)); when(returnedTransaction.getTransaction()).thenReturn(justTransaction); + when(returnedTransaction.getBlockHash()).thenReturn(Optional.of(Hash.ZERO)); final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput(); privateTransaction.writeTo(bvrlp); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index acce6a1e3..33d160561 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -47,6 +47,7 @@ public enum RpcMethod { PRIV_DISTRIBUTE_RAW_TRANSACTION("priv_distributeRawTransaction"), PRIV_GET_EEA_TRANSACTION_COUNT("priv_getEeaTransactionCount"), PRIV_GET_CODE("priv_getCode"), + PRIVX_FIND_PRIVACY_GROUP("privx_findOnChainPrivacyGroup"), EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"), ETH_ACCOUNTS("eth_accounts"), ETH_BLOCK_NUMBER("eth_blockNumber"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java index 26b9aec23..c4de22921 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransaction.java @@ -20,6 +20,8 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter.co import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.ENCLAVE_ERROR; +import org.hyperledger.besu.enclave.EnclaveClientException; +import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -27,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.Enclav import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; @@ -37,6 +40,8 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPException; +import java.util.Optional; + import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -70,6 +75,10 @@ public class EeaSendRawTransaction implements JsonRpcMethod { final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(RLP.input(Bytes.fromHexString(rawPrivateTransaction))); + final Optional addPayloadEnclaveKey = + privacyController.buildAndSendAddPayload( + privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + final String enclavePublicKey = enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()); final ValidationResult validationResult = @@ -81,8 +90,34 @@ public class EeaSendRawTransaction implements JsonRpcMethod { final String enclaveKey = privacyController.sendTransaction(privateTransaction, enclavePublicKey); - final Transaction privacyMarkerTransaction = - privacyController.createPrivacyMarkerTransaction(enclaveKey, privateTransaction); + final Transaction privacyMarkerTransaction; + if (privateTransaction.getPrivacyGroupId().isPresent()) { + PrivacyGroup privacyGroup = null; + try { + privacyGroup = + privacyController.retrievePrivacyGroup( + privateTransaction.getPrivacyGroupId().get().toBase64String(), + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + } catch (final EnclaveClientException e) { + // it is an onchain group + } + if (privacyGroup == null + || !privacyGroup + .getMembers() + .contains(enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()))) { + privacyMarkerTransaction = + privacyController.createPrivacyMarkerTransaction( + buildCompoundKey(enclaveKey, addPayloadEnclaveKey), + privateTransaction, + Address.ONCHAIN_PRIVACY); + } else { + privacyMarkerTransaction = + privacyController.createPrivacyMarkerTransaction(enclaveKey, privateTransaction); + } + } else { + privacyMarkerTransaction = + privacyController.createPrivacyMarkerTransaction(enclaveKey, privateTransaction); + } return transactionPool .addLocalTransaction(privacyMarkerTransaction) @@ -99,4 +134,14 @@ public class EeaSendRawTransaction implements JsonRpcMethod { return new JsonRpcErrorResponse(id, convertEnclaveInvalidReason(e.getMessage())); } } + + private String buildCompoundKey( + final String enclaveKey, final Optional addPayloadEnclaveKey) { + return addPayloadEnclaveKey.isPresent() + ? Bytes.concatenate( + Bytes.fromBase64String(enclaveKey), + Bytes.fromBase64String(addPayloadEnclaveKey.get())) + .toBase64String() + : enclaveKey; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java index 583e35a09..3b2fe246c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import java.util.Arrays; +import java.util.List; import org.apache.logging.log4j.Logger; @@ -58,12 +59,13 @@ public class PrivFindPrivacyGroup implements JsonRpcMethod { LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); - PrivacyGroup[] response; + final List response; try { response = - privacyController.findPrivacyGroup( - Arrays.asList(addresses), - enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + Arrays.asList( + privacyController.findPrivacyGroup( + Arrays.asList(addresses), + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()))); } catch (final MultiTenancyValidationException e) { LOG.error("Unauthorized privacy multi-tenancy rpc request. {}", e.getMessage()); return new JsonRpcErrorResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java index 991e5892e..df0f62aec 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java @@ -17,7 +17,9 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.logging.log4j.LogManager.getLogger; +import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -33,10 +35,17 @@ import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import java.util.List; +import java.util.Optional; + import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class PrivGetPrivateTransaction implements JsonRpcMethod { @@ -44,14 +53,17 @@ public class PrivGetPrivateTransaction implements JsonRpcMethod { private final BlockchainQueries blockchain; private final PrivacyController privacyController; + private final PrivateStateStorage privateStateStorage; private final EnclavePublicKeyProvider enclavePublicKeyProvider; public PrivGetPrivateTransaction( final BlockchainQueries blockchain, final PrivacyController privacyController, + final PrivateStateStorage privateStateStorage, final EnclavePublicKeyProvider enclavePublicKeyProvider) { this.blockchain = blockchain; this.privacyController = privacyController; + this.privateStateStorage = privateStateStorage; this.enclavePublicKeyProvider = enclavePublicKeyProvider; } @@ -71,32 +83,101 @@ public class PrivGetPrivateTransaction implements JsonRpcMethod { if (resultTransaction == null) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), null); } + + final String payloadKey = + resultTransaction.getTransaction().getPayload().slice(0, 32).toBase64String(); + final String enclaveKey = enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()); + final Optional privateTransaction; try { - LOG.trace("Fetching transaction information"); - final ReceiveResponse receiveResponse = - privacyController.retrieveTransaction( - resultTransaction.getTransaction().getPayload().toBase64String(), - enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); - LOG.trace("Received transaction information"); + privateTransaction = + findPrivateTransactionInEnclave( + payloadKey, + resultTransaction.getTransaction().getHash(), + enclaveKey, + resultTransaction.getBlockHash().get()); + } catch (final EnclaveClientException e) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), + JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason(e.getMessage())); + } - final BytesValueRLPInput input = - new BytesValueRLPInput( - Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); - - final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(input); - if (privateTransaction.getPrivacyGroupId().isPresent()) { + if (privateTransaction.isPresent()) { + if (privateTransaction.get().getPrivacyGroupId().isPresent()) { return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), - new PrivateTransactionGroupResult(privateTransaction)); + new PrivateTransactionGroupResult(privateTransaction.get())); } else { return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), - new PrivateTransactionLegacyResult(privateTransaction)); + new PrivateTransactionLegacyResult(privateTransaction.get())); } - } catch (final Exception e) { - LOG.error("Failed to fetch private transaction", e); + } else { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), JsonRpcError.ENCLAVE_ERROR); } } + + private Optional findPrivateTransactionInEnclave( + final String payloadKey, + final Hash pmtTransactionHash, + final String enclaveKey, + final Bytes32 blockHash) { + PrivateTransaction privateTransaction; + try { + LOG.trace("Fetching transaction information"); + final ReceiveResponse receiveResponse = + privacyController.retrieveTransaction(payloadKey, enclaveKey); + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); + input.enterList(); + if (input.nextIsList()) { + privateTransaction = PrivateTransaction.readFrom(input); + input.leaveListLenient(); + } else { + input.reset(); + privateTransaction = PrivateTransaction.readFrom(input); + } + LOG.trace("Received transaction information"); + } catch (final EnclaveClientException e) { + Optional privateTransactionOptional = Optional.empty(); + if (e.getMessage().equals("EnclavePayloadNotFound")) { + privateTransactionOptional = fetchPayloadFromAddBlob(blockHash, pmtTransactionHash); + } + if (privateTransactionOptional.isEmpty()) { + throw e; + } else { + privateTransaction = privateTransactionOptional.get(); + } + } + return Optional.ofNullable(privateTransaction); + } + + private Optional fetchPayloadFromAddBlob( + final Bytes32 blockHash, final Hash expectedPrivacyMarkerTransactionHash) { + LOG.trace("Fetching transaction information from add blob"); + final Optional privacyGroupHeadBlockMapOptional = + privateStateStorage.getPrivacyGroupHeadBlockMap(blockHash); + if (privacyGroupHeadBlockMapOptional.isPresent()) { + for (final Bytes32 privacyGroupId : privacyGroupHeadBlockMapOptional.get().keySet()) { + final Optional addDataKey = privateStateStorage.getAddDataKey(privacyGroupId); + if (addDataKey.isPresent()) { + final List privateTransactionWithMetadataList = + privacyController.retrieveAddBlob(addDataKey.get().toBase64String()); + for (final PrivateTransactionWithMetadata privateTransactionWithMetadata : + privateTransactionWithMetadataList) { + final Hash actualPrivacyMarkerTransactionHash = + privateTransactionWithMetadata + .getPrivateTransactionMetadata() + .getPrivacyMarkerTransactionHash(); + if (expectedPrivacyMarkerTransactionHash.equals(actualPrivacyMarkerTransactionHash)) { + return Optional.of(privateTransactionWithMetadata.getPrivateTransaction()); + } + } + } + } + } + return Optional.empty(); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java index 28c1fb9eb..9c8d0c3e8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java @@ -39,9 +39,12 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLP; +import java.util.List; import java.util.Optional; import org.apache.logging.log4j.Logger; @@ -88,72 +91,146 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod { final Transaction pmtTransaction = blockBody.getTransactions().get(pmtLocation.getTransactionIndex()); - final Hash blockhash = pmtLocation.getBlockHash(); - final long blockNumber = blockchain.getBlockchain().getBlockHeader(blockhash).get().getNumber(); + final Hash blockHash = pmtLocation.getBlockHash(); + final long blockNumber = blockchain.getBlockchain().getBlockHeader(blockHash).get().getNumber(); - final PrivateTransaction privateTransaction; - final String privacyGroupId; + final String payloadKey = pmtTransaction.getPayload().slice(0, 32).toBase64String(); + final String enclaveKey = enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()); + + final Optional privateTransactionOptional; try { - final ReceiveResponse receiveResponse = - privacyController.retrieveTransaction( - pmtTransaction.getPayload().toBase64String(), - enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); - LOG.trace("Received private transaction information"); - - final BytesValueRLPInput input = - new BytesValueRLPInput( - Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); - - privateTransaction = PrivateTransaction.readFrom(input); - privacyGroupId = receiveResponse.getPrivacyGroupId(); + privateTransactionOptional = + findPrivateTransactionInEnclave( + payloadKey, pmtTransactionHash, enclaveKey, pmtLocation.getBlockHash()); } catch (final EnclaveClientException e) { return handleEnclaveException(requestContext, e); } - final String contractAddress = - privateTransaction.getTo().isEmpty() - ? Address.privateContractAddress( - privateTransaction.getSender(), - privateTransaction.getNonce(), - Bytes.fromBase64String(privacyGroupId)) - .toString() - : null; + final String privacyGroupId; + if (privateTransactionOptional.isPresent()) { + final PrivateTransaction privateTransaction = privateTransactionOptional.get(); + try { + if (privateTransaction.getPrivacyGroupId().isPresent()) { + privacyGroupId = privateTransaction.getPrivacyGroupId().get().toBase64String(); + } else { + privacyGroupId = + privacyController.retrieveTransaction(payloadKey, enclaveKey).getPrivacyGroupId(); + } + } catch (final EnclaveClientException e) { + return handleEnclaveException(requestContext, e); + } + final String contractAddress = + privateTransaction.getTo().isEmpty() + ? Address.privateContractAddress( + privateTransaction.getSender(), + privateTransaction.getNonce(), + Bytes.fromBase64String(privacyGroupId)) + .toString() + : null; - LOG.trace("Calculated contractAddress: {}", contractAddress); + LOG.trace("Calculated contractAddress: {}", contractAddress); - final Bytes rlpEncoded = RLP.encode(privateTransaction::writeTo); - final Bytes32 txHash = org.hyperledger.besu.crypto.Hash.keccak256(rlpEncoded); - LOG.trace("Calculated private transaction hash: {}", txHash); + final Bytes rlpEncoded = RLP.encode(privateTransaction::writeTo); + final Bytes32 txHash = org.hyperledger.besu.crypto.Hash.keccak256(rlpEncoded); + LOG.trace("Calculated private transaction hash: {}", txHash); - final PrivateTransactionReceipt privateTransactioReceipt = - privacyParameters - .getPrivateStateStorage() - .getTransactionReceipt(blockhash, txHash) - .orElse(PrivateTransactionReceipt.EMPTY); + final PrivateTransactionReceipt privateTransactioReceipt = + privacyParameters + .getPrivateStateStorage() + .getTransactionReceipt(blockHash, txHash) + .orElse(PrivateTransactionReceipt.FAILED); - LOG.trace("Processed private transaction receipt"); + LOG.trace("Processed private transaction receipt"); - final PrivateTransactionReceiptResult result = - new PrivateTransactionReceiptResult( - contractAddress, - privateTransaction.getSender().toString(), - privateTransaction.getTo().map(Address::toString).orElse(null), - privateTransactioReceipt.getLogs(), - privateTransactioReceipt.getOutput(), - blockhash, - blockNumber, - pmtLocation.getTransactionIndex(), - pmtTransaction.getHash(), - privateTransaction.getHash(), - privateTransaction.getPrivateFrom(), - privateTransaction.getPrivateFor().orElse(null), - privateTransaction.getPrivacyGroupId().orElse(null), - privateTransactioReceipt.getRevertReason().orElse(null), - Quantity.create(privateTransactioReceipt.getStatus())); + final PrivateTransactionReceiptResult result = + new PrivateTransactionReceiptResult( + contractAddress, + privateTransaction.getSender().toString(), + privateTransaction.getTo().map(Address::toString).orElse(null), + privateTransactioReceipt.getLogs(), + privateTransactioReceipt.getOutput(), + blockHash, + blockNumber, + pmtLocation.getTransactionIndex(), + pmtTransaction.getHash(), + privateTransaction.getHash(), + privateTransaction.getPrivateFrom(), + privateTransaction.getPrivateFor().orElse(null), + privateTransaction.getPrivacyGroupId().orElse(null), + privateTransactioReceipt.getRevertReason().orElse(null), + Quantity.create(privateTransactioReceipt.getStatus())); - LOG.trace("Created Private Transaction Receipt Result from given Transaction Hash"); + LOG.trace("Created Private Transaction Receipt Result from given Transaction Hash"); - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); + } else { + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), null); + } + } + + private Optional findPrivateTransactionInEnclave( + final String payloadKey, + final Hash pmtTransactionHash, + final String enclaveKey, + final Bytes32 blockHash) { + PrivateTransaction privateTransaction; + try { + LOG.trace("Fetching transaction information"); + final ReceiveResponse receiveResponse = + privacyController.retrieveTransaction(payloadKey, enclaveKey); + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); + input.enterList(); + if (input.nextIsList()) { + privateTransaction = PrivateTransaction.readFrom(input); + input.leaveListLenient(); + } else { + input.reset(); + privateTransaction = PrivateTransaction.readFrom(input); + } + LOG.trace("Received transaction information"); + } catch (final EnclaveClientException e) { + Optional privateTransactionOptional = Optional.empty(); + if (e.getMessage().equals("EnclavePayloadNotFound")) { + privateTransactionOptional = fetchPayloadFromAddBlob(blockHash, pmtTransactionHash); + } + if (privateTransactionOptional.isEmpty()) { + throw e; + } else { + privateTransaction = privateTransactionOptional.get(); + } + } + return Optional.ofNullable(privateTransaction); + } + + private Optional fetchPayloadFromAddBlob( + final Bytes32 blockHash, final Hash expectedPrivacyMarkerTransactionHash) { + LOG.trace("Fetching transaction information from add blob"); + final Optional privacyGroupHeadBlockMapOptional = + privacyParameters.getPrivateStateStorage().getPrivacyGroupHeadBlockMap(blockHash); + if (privacyGroupHeadBlockMapOptional.isPresent()) { + for (final Bytes32 privacyGroupId : privacyGroupHeadBlockMapOptional.get().keySet()) { + final Optional addDataKey = + privacyParameters.getPrivateStateStorage().getAddDataKey(privacyGroupId); + if (addDataKey.isPresent()) { + final List privateTransactionWithMetadataList = + privacyController.retrieveAddBlob(addDataKey.get().toBase64String()); + for (final PrivateTransactionWithMetadata privateTransactionWithMetadata : + privateTransactionWithMetadataList) { + final Hash actualPrivacyMarkerTransactionHash = + privateTransactionWithMetadata + .getPrivateTransactionMetadata() + .getPrivacyMarkerTransactionHash(); + if (expectedPrivacyMarkerTransactionHash.equals(actualPrivacyMarkerTransactionHash)) { + return Optional.of(privateTransactionWithMetadata.getPrivateTransaction()); + } + } + } + } + } + return Optional.empty(); } private JsonRpcResponse handleEnclaveException( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java new file mode 100644 index 000000000..7f0d2e46e --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnChainPrivacyGroup.java @@ -0,0 +1,66 @@ +/* + * 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.api.jsonrpc.internal.privacy.methods.privx; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import org.hyperledger.besu.enclave.types.PrivacyGroup; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; + +import java.util.Arrays; +import java.util.List; + +import org.apache.logging.log4j.Logger; + +public class PrivxFindOnChainPrivacyGroup implements JsonRpcMethod { + + private static final Logger LOG = getLogger(); + private final PrivacyController privacyController; + private final EnclavePublicKeyProvider enclavePublicKeyProvider; + + public PrivxFindOnChainPrivacyGroup( + final PrivacyController privacyController, + final EnclavePublicKeyProvider enclavePublicKeyProvider) { + this.privacyController = privacyController; + this.enclavePublicKeyProvider = enclavePublicKeyProvider; + } + + @Override + public String getName() { + return RpcMethod.PRIVX_FIND_PRIVACY_GROUP.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + LOG.trace("Executing {}", RpcMethod.PRIVX_FIND_PRIVACY_GROUP.getMethodName()); + + final String[] addresses = requestContext.getRequiredParameter(0, String[].class); + + LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); + + final List response = + privacyController.findOnChainPrivacyGroup( + Arrays.asList(addresses), + enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser())); + + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index c57998515..71c712fae 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -105,6 +105,8 @@ public class JsonRpcMethodsFactory { new PermJsonRpcMethods(accountsWhitelistController, nodeWhitelistController), new PrivJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), + new PrivxJsonRpcMethods( + blockchainQueries, protocolSchedule, transactionPool, privacyParameters), new Web3JsonRpcMethods(clientVersion), // TRACE Methods (Disabled while under development) new TraceJsonRpcMethods(blockchainQueries, protocolSchedule), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java index 3adf1eed5..47ebcc3d1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java @@ -67,7 +67,10 @@ public class PrivJsonRpcMethods extends PrivacyApiGroupJsonRpcMethods { new PrivGetPrivacyPrecompileAddress(getPrivacyParameters()), new PrivGetTransactionCount(privacyController, enclavePublicKeyProvider), new PrivGetPrivateTransaction( - getBlockchainQueries(), privacyController, enclavePublicKeyProvider), + getBlockchainQueries(), + privacyController, + getPrivacyParameters().getPrivateStateStorage(), + enclavePublicKeyProvider), new PrivDistributeRawTransaction(privacyController, enclavePublicKeyProvider), new PrivCall(getBlockchainQueries(), privacyController, enclavePublicKeyProvider), new PrivGetCode(getBlockchainQueries(), privacyController, enclavePublicKeyProvider)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java index c69d299e9..d06cd2909 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java @@ -146,6 +146,7 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho final PrivateMarkerTransactionFactory markerTransactionFactory) { final DefaultPrivacyController defaultPrivacyController = new DefaultPrivacyController( + getBlockchainQueries().getBlockchain(), privacyParameters, protocolSchedule.getChainId(), markerTransactionFactory, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivxJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivxJsonRpcMethods.java new file mode 100644 index 000000000..eca05e0fa --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivxJsonRpcMethods.java @@ -0,0 +1,51 @@ +/* + * 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.api.jsonrpc.methods; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.EnclavePublicKeyProvider; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.privx.PrivxFindOnChainPrivacyGroup; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; + +import java.util.Map; + +public class PrivxJsonRpcMethods extends PrivacyApiGroupJsonRpcMethods { + + public PrivxJsonRpcMethods( + final BlockchainQueries blockchainQueries, + final ProtocolSchedule protocolSchedule, + final TransactionPool transactionPool, + final PrivacyParameters privacyParameters) { + super(blockchainQueries, protocolSchedule, transactionPool, privacyParameters); + } + + @Override + protected RpcApi getApiGroup() { + return RpcApis.PRIV; + } + + @Override + protected Map create( + final PrivacyController privacyController, + final EnclavePublicKeyProvider enclavePublicKeyProvider) { + return mapOf(new PrivxFindOnChainPrivacyGroup(privacyController, enclavePublicKeyProvider)); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java index 3e5b1a65e..db1bb7694 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.eea; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED; @@ -26,6 +27,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; @@ -199,6 +201,10 @@ public class EeaSendRawTransactionTest { .thenReturn(MOCK_ORION_KEY); when(privacyController.validatePrivateTransaction(any(PrivateTransaction.class), anyString())) .thenReturn(ValidationResult.valid()); + when(privacyController.retrievePrivacyGroup(any(String.class), any(String.class))) + .thenReturn( + new PrivacyGroup( + "", PrivacyGroup.Type.PANTHEON, "", "", singletonList(ENCLAVE_PUBLIC_KEY))); when(privacyController.createPrivacyMarkerTransaction( any(String.class), any(PrivateTransaction.class))) .thenReturn(PUBLIC_TRANSACTION); @@ -228,6 +234,43 @@ public class EeaSendRawTransactionTest { verify(transactionPool).addLocalTransaction(any(Transaction.class)); } + @Test + public void validOnChainTransactionPrivacyGroupIsSentToTransactionPool() { + when(privacyController.sendTransaction(any(PrivateTransaction.class), any())) + .thenReturn(MOCK_ORION_KEY); + when(privacyController.validatePrivateTransaction( + any(PrivateTransaction.class), any(String.class))) + .thenReturn(ValidationResult.valid()); + when(privacyController.createPrivacyMarkerTransaction( + any(String.class), any(PrivateTransaction.class), any(Address.class))) + .thenReturn(PUBLIC_TRANSACTION); + when(transactionPool.addLocalTransaction(any(Transaction.class))) + .thenReturn(ValidationResult.valid()); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + "eea_sendRawTransaction", + new String[] {VALID_PRIVATE_TRANSACTION_RLP_PRIVACY_GROUP})); + + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse( + request.getRequest().getId(), + "0x221e930a2c18d91fca4d509eaa3512f3e01fef266f660e32473de67474b36c15"); + + final JsonRpcResponse actualResponse = method.response(request); + + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + verify(privacyController).sendTransaction(any(PrivateTransaction.class), any()); + verify(privacyController) + .validatePrivateTransaction(any(PrivateTransaction.class), any(String.class)); + verify(privacyController) + .createPrivacyMarkerTransaction( + any(String.class), any(PrivateTransaction.class), eq(Address.ONCHAIN_PRIVACY)); + verify(transactionPool).addLocalTransaction(any(Transaction.class)); + } + @Test public void invalidTransactionWithoutPrivateFromFieldFailsWithDecodeError() { final JsonRpcRequestContext request = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java index 3d2477740..fd75c662c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroupTest.java @@ -73,6 +73,7 @@ public class PrivFindPrivacyGroupTest { privacyGroup.setMembers(Lists.list("member1")); } + @SuppressWarnings("unchecked") @Test public void findsPrivacyGroupWithValidAddresses() { when(privacyController.findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY)) @@ -83,9 +84,9 @@ public class PrivFindPrivacyGroupTest { final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) privFindPrivacyGroup.response(request); - final PrivacyGroup[] result = (PrivacyGroup[]) response.getResult(); + final List result = (List) response.getResult(); assertThat(result).hasSize(1); - assertThat(result[0]).isEqualToComparingFieldByField(privacyGroup); + assertThat(result.get(0)).isEqualToComparingFieldByField(privacyGroup); verify(privacyController).findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransactionTest.java index a70dbf882..b13f95517 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransactionTest.java @@ -46,13 +46,15 @@ import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.Restriction; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.math.BigInteger; import java.util.Base64; +import java.util.Collections; +import java.util.List; import java.util.Optional; -import com.google.common.collect.Lists; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.User; import io.vertx.ext.auth.jwt.impl.JWTUser; @@ -78,6 +80,9 @@ public class PrivGetPrivateTransactionTest { Bytes.fromBase64String("5bpr9tz4zhmWmk9RlNng93Ky7lXwFkMc7+ckoFgUMku=").toString(); private static final Bytes ENCLAVE_KEY = Bytes.fromBase64String("93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="); + private static final List TEST_PRIVATE_FOR = + Collections.singletonList( + Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=")); private final PrivateTransaction.Builder privateTransactionBuilder = PrivateTransaction.builder() @@ -105,6 +110,7 @@ public class PrivGetPrivateTransactionTest { private final Enclave enclave = mock(Enclave.class); private final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); + private final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class); private final BlockchainQueries blockchain = mock(BlockchainQueries.class); private final TransactionWithMetadata returnedTransaction = mock(TransactionWithMetadata.class); private final Transaction justTransaction = mock(Transaction.class); @@ -117,6 +123,7 @@ public class PrivGetPrivateTransactionTest { public void before() { when(privacyParameters.getEnclave()).thenReturn(enclave); when(privacyParameters.isEnabled()).thenReturn(true); + when(returnedTransaction.getBlockHash()).thenReturn(Optional.of(Hash.ZERO)); } @Test @@ -127,16 +134,13 @@ public class PrivGetPrivateTransactionTest { when(justTransaction.getPayload()).thenReturn(ENCLAVE_KEY); final PrivateTransaction privateTransaction = - privateTransactionBuilder - .privateFor( - Lists.newArrayList( - Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="))) - .signAndBuild(KEY_PAIR); + privateTransactionBuilder.privateFor(TEST_PRIVATE_FOR).signAndBuild(KEY_PAIR); final PrivateTransactionLegacyResult privateTransactionLegacyResult = new PrivateTransactionLegacyResult(privateTransaction); final PrivGetPrivateTransaction privGetPrivateTransaction = - new PrivGetPrivateTransaction(blockchain, privacyController, enclavePublicKeyProvider); + new PrivGetPrivateTransaction( + blockchain, privacyController, privateStateStorage, enclavePublicKeyProvider); final Object[] params = new Object[] {TRANSACTION_HASH}; final JsonRpcRequestContext request = new JsonRpcRequestContext( @@ -163,17 +167,16 @@ public class PrivGetPrivateTransactionTest { when(blockchain.transactionByHash(any(Hash.class))) .thenReturn(Optional.of(returnedTransaction)); when(returnedTransaction.getTransaction()).thenReturn(justTransaction); - when(justTransaction.getPayload()).thenReturn(Bytes.fromBase64String("")); + when(justTransaction.getPayload()).thenReturn(ENCLAVE_KEY); final PrivateTransaction privateTransaction = - privateTransactionBuilder - .privacyGroupId(Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=")) - .signAndBuild(KEY_PAIR); + privateTransactionBuilder.privacyGroupId(ENCLAVE_KEY).signAndBuild(KEY_PAIR); final PrivateTransactionGroupResult privateTransactionGroupResult = new PrivateTransactionGroupResult(privateTransaction); final PrivGetPrivateTransaction privGetPrivateTransaction = - new PrivGetPrivateTransaction(blockchain, privacyController, enclavePublicKeyProvider); + new PrivGetPrivateTransaction( + blockchain, privacyController, privateStateStorage, enclavePublicKeyProvider); final Object[] params = new Object[] {TRANSACTION_HASH}; final JsonRpcRequestContext request = @@ -200,15 +203,14 @@ public class PrivGetPrivateTransactionTest { when(blockchain.transactionByHash(any(Hash.class))) .thenReturn(Optional.of(returnedTransaction)); when(returnedTransaction.getTransaction()).thenReturn(justTransaction); - when(justTransaction.getPayload()).thenReturn(Bytes.fromBase64String("")); + when(justTransaction.getPayload()).thenReturn(ENCLAVE_KEY); final PrivateTransaction privateTransaction = - privateTransactionBuilder - .privacyGroupId(Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=")) - .signAndBuild(KEY_PAIR); + privateTransactionBuilder.privacyGroupId(ENCLAVE_KEY).signAndBuild(KEY_PAIR); final PrivGetPrivateTransaction privGetPrivateTransaction = - new PrivGetPrivateTransaction(blockchain, privacyController, enclavePublicKeyProvider); + new PrivGetPrivateTransaction( + blockchain, privacyController, privateStateStorage, enclavePublicKeyProvider); final Object[] params = new Object[] {TRANSACTION_HASH}; final JsonRpcRequestContext request = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceiptTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceiptTest.java index 4e1035bbd..e8c5183dd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceiptTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceiptTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -49,14 +50,13 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; import org.hyperledger.besu.ethereum.privacy.Restriction; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; -import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.math.BigInteger; import java.util.Base64; import java.util.Collections; import java.util.Optional; -import com.google.common.collect.Lists; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.User; import io.vertx.ext.auth.jwt.impl.JWTUser; @@ -72,7 +72,8 @@ public class PrivGetTransactionReceiptTest { @Rule public final TemporaryFolder temp = new TemporaryFolder(); private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private static final Bytes ENCLAVE_KEY = Bytes.wrap("EnclaveKey".getBytes(UTF_8)); + private static final Bytes ENCLAVE_KEY = + Bytes.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)); private static final Address SENDER = Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"); @@ -103,10 +104,8 @@ public class PrivGetTransactionReceiptTest { + "daa4f6b2f003d1b0180029")) .sender(SENDER) .chainId(BigInteger.valueOf(2018)) - .privateFrom(Bytes.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8))) - .privateFor( - Lists.newArrayList( - Bytes.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8)))) + .privateFrom(Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY)) + .privateFor(Collections.singletonList(ENCLAVE_KEY)) .restriction(Restriction.RESTRICTED) .signAndBuild(KEY_PAIR); @@ -115,9 +114,9 @@ public class PrivGetTransactionReceiptTest { .nonce(0) .gasPrice(Wei.of(1000)) .gasLimit(3000000) - .to(Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57")) + .to(Address.DEFAULT_PRIVACY) .value(Wei.ZERO) - .payload(Bytes.wrap("EnclaveKey".getBytes(UTF_8))) + .payload(ENCLAVE_KEY) .sender(SENDER) .chainId(BigInteger.valueOf(2018)) .signAndBuild(KEY_PAIR); @@ -125,20 +124,17 @@ public class PrivGetTransactionReceiptTest { private final PrivateTransactionReceiptResult expectedResult = new PrivateTransactionReceiptResult( "0x0bac79b78b9866ef11c989ad21a7fcf15f7a18d7", - "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", + SENDER.toHexString(), null, Collections.emptyList(), Bytes.EMPTY, null, 0, 0, - Hash.fromHexString("0x65348ddfe0b282c26862b4610a8c45fd8486a93ae6e2b197836c826b4b671848"), - Hash.fromHexString("0x43ef5094212ba4862d6b310a3d337c3478fdf942c5ed3f8e792ad93d6d96994d"), - Bytes.fromHexString( - "0x41316156744d784c4355486d425648586f5a7a7a42675062572f776a3561784470573958386c393153476f3d"), - Collections.singletonList( - Bytes.fromHexString( - "0x4b6f32625671442b6e4e6c4e594c35454537793349644f6e766966746a69697a706a52742b4854754642733d")), + transaction.getHash(), + privateTransaction.getHash(), + Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY), + Collections.singletonList(ENCLAVE_KEY), null, null, Quantity.create(Bytes.of(1).toUnsignedBigInteger())); @@ -155,12 +151,13 @@ public class PrivGetTransactionReceiptTest { @Before public void setUp() { - when(privacyController.retrieveTransaction(anyString(), any())) - .thenReturn( - new ReceiveResponse( - Base64.getEncoder().encode(RLP.encode(privateTransaction::writeTo).toArray()), - "", - null)); + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + privateTransaction.writeTo(rlpOutput); + rlpOutput.endList(); + final byte[] src = rlpOutput.encoded().toArray(); + when(privacyController.retrieveTransaction(anyString(), anyString())) + .thenReturn(new ReceiveResponse(Base64.getEncoder().encode(src), "", null)); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); final TransactionLocation transactionLocation = new TransactionLocation(Hash.EMPTY, 0); @@ -200,7 +197,9 @@ public class PrivGetTransactionReceiptTest { (PrivateTransactionReceiptResult) response.getResult(); assertThat(result).isEqualToComparingFieldByField(expectedResult); - verify(privacyController).retrieveTransaction(ENCLAVE_KEY.toBase64String(), ENCLAVE_PUBLIC_KEY); + verify(privacyController, times(2)) + .retrieveTransaction( + transaction.getPayload().slice(0, 32).toBase64String(), ENCLAVE_PUBLIC_KEY); } @Test diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java index e22b7e493..047e8fbeb 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java @@ -14,7 +14,6 @@ */ 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.catchThrowable; import static org.mockito.ArgumentMatchers.any; @@ -37,6 +36,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; 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; @@ -45,7 +45,6 @@ import org.hyperledger.orion.testutil.OrionKeyConfiguration; import org.hyperledger.orion.testutil.OrionTestHarness; import org.hyperledger.orion.testutil.OrionTestHarnessFactory; -import java.util.Base64; import java.util.List; import java.util.Optional; @@ -63,21 +62,18 @@ public class PrivacyPrecompiledContractIntegrationTest { @ClassRule public static final TemporaryFolder folder = new TemporaryFolder(); - private static final byte[] VALID_PRIVATE_TRANSACTION_RLP_BASE64 = - Base64.getEncoder() - .encode( - Bytes.fromHexString( - "0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" - + "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - + "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" - + "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" - + "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" - + "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" - + "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" - + "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" - + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" - + "64") - .toArray()); + private static final Bytes VALID_PRIVATE_TRANSACTION_RLP = + Bytes.fromHexString( + "0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" + + "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + + "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" + + "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" + + "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" + + "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" + + "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" + + "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" + + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" + + "64"); private static final String DEFAULT_OUTPUT = "0x01"; private static Enclave enclave; @@ -170,7 +166,10 @@ public class PrivacyPrecompiledContractIntegrationTest { public void testSendAndReceive() { final List publicKeys = testHarness.getPublicKeys(); - final String s = new String(VALID_PRIVATE_TRANSACTION_RLP_BASE64, UTF_8); + final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); + bytesValueRLPOutput.writeRLP(VALID_PRIVATE_TRANSACTION_RLP); + + final String s = bytesValueRLPOutput.encoded().toBase64String(); final SendResponse sr = enclave.send(s, publicKeys.get(0), Lists.newArrayList(publicKeys.get(0))); @@ -191,7 +190,7 @@ public class PrivacyPrecompiledContractIntegrationTest { final List publicKeys = testHarness.getPublicKeys(); publicKeys.add("noPrivateKey"); - final String s = new String(VALID_PRIVATE_TRANSACTION_RLP_BASE64, UTF_8); + final String s = VALID_PRIVATE_TRANSACTION_RLP.toBase64String(); final Throwable thrown = catchThrowable(() -> enclave.send(s, publicKeys.get(0), publicKeys)); @@ -203,7 +202,7 @@ public class PrivacyPrecompiledContractIntegrationTest { final List publicKeys = testHarness.getPublicKeys(); publicKeys.add("noPrivateKenoPrivateKenoPrivateKenoPrivateK"); - final String s = new String(VALID_PRIVATE_TRANSACTION_RLP_BASE64, UTF_8); + final String s = VALID_PRIVATE_TRANSACTION_RLP.toBase64String(); final Throwable thrown = catchThrowable(() -> enclave.send(s, publicKeys.get(0), publicKeys)); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Address.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Address.java index 092c02b6c..3a19e3b41 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Address.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Address.java @@ -45,6 +45,9 @@ public class Address extends DelegatingBytes implements org.hyperledger.besu.plu // Last address that can be generated for a pre-compiled contract public static final Integer PRIVACY = Byte.MAX_VALUE - 1; public static final Address DEFAULT_PRIVACY = Address.precompiled(PRIVACY); + public static final Address ONCHAIN_PRIVACY = Address.precompiled(PRIVACY - 1); + public static final Address PRIVACY_PROXY = Address.precompiled(PRIVACY - 2); + public static final Address DEFAULT_PRIVACY_MANAGEMENT = Address.precompiled(PRIVACY - 3); public static final Address ZERO = Address.fromHexString("0x0"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetMessageCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetMessageCallProcessor.java index c21ee2ebe..12ddb143a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetMessageCallProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetMessageCallProcessor.java @@ -133,7 +133,7 @@ public class MainnetMessageCallProcessor extends AbstractMessageProcessor { final Bytes output = contract.compute(frame.getInputData(), frame); operationTracer.tracePrecompileCall(frame, gasRequirement, output); if (output != null) { - if (contract.getName().equals("Privacy")) { + if (contract.getName().equals("Privacy") || contract.getName().equals("OnChainPrivacy")) { // do not decrement the gas requirement for a privacy pre-compile contract call -> leads // to discrepancies in receipts root between public and private nodes in a network. frame.incrementRemainingGas(gasRequirement); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java index d3f9329dc..3de1d9eea 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.precompiles.ECRECPrecompiledContrac import org.hyperledger.besu.ethereum.mainnet.precompiles.IDPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.RIPEMD160PrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.SHA256PrecompiledContract; +import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.OnChainPrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.PrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.vm.GasCalculator; @@ -124,6 +125,12 @@ public abstract class MainnetPrecompiledContractRegistries { new PrivacyPrecompiledContract( precompiledContractConfiguration.getGasCalculator(), precompiledContractConfiguration.getPrivacyParameters())); + registry.put( + Address.ONCHAIN_PRIVACY, + accountVersion, + new OnChainPrivacyPrecompiledContract( + precompiledContractConfiguration.getGasCalculator(), + precompiledContractConfiguration.getPrivacyParameters())); return registry; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java index b2de2e626..e8ea3db5d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MutableProtocolSchedule.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet; import static com.google.common.base.Preconditions.checkArgument; import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.math.BigInteger; import java.util.Comparator; @@ -78,4 +79,13 @@ public class MutableProtocolSchedule implements ProtocolSchedule { public void setTransactionFilter(final TransactionFilter transactionFilter) { protocolSpecs.forEach(spec -> spec.getSpec().setTransactionFilter(transactionFilter)); } + + @Override + public void setPublicWorldStateArchiveForPrivacyBlockProcessor( + final WorldStateArchive publicWorldStateArchive) { + protocolSpecs.forEach( + spec -> + spec.getSpec() + .setPublicWorldStateArchiveForPrivacyBlockProcessor(publicWorldStateArchive)); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java index 3f77dcadf..533549a40 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java @@ -14,23 +14,52 @@ */ package org.hyperledger.besu.ethereum.mainnet; +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; +import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.privacy.PrivateStateRehydration; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import java.util.ArrayList; +import java.util.Base64; import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class PrivacyBlockProcessor implements BlockProcessor { private final BlockProcessor blockProcessor; + private final ProtocolSchedule protocolSchedule; + private final Enclave enclave; private final PrivateStateStorage privateStateStorage; + private final WorldStateArchive privateWorldStateArchive; + private WorldStateArchive publicWorldStateArchive; - public PrivacyBlockProcessor( - final BlockProcessor blockProcessor, final PrivateStateStorage privateStateStorage) { + public PrivacyBlockProcessor( + final BlockProcessor blockProcessor, + final ProtocolSchedule protocolSchedule, + final Enclave enclave, + final PrivateStateStorage privateStateStorage, + final WorldStateArchive privateWorldStateArchive) { this.blockProcessor = blockProcessor; + this.protocolSchedule = protocolSchedule; + this.enclave = enclave; this.privateStateStorage = privateStateStorage; + this.privateWorldStateArchive = privateWorldStateArchive; + } + + public void setPublicWorldStateArchive(final WorldStateArchive publicWorldStateArchive) { + this.publicWorldStateArchive = publicWorldStateArchive; } @Override @@ -40,6 +69,47 @@ public class PrivacyBlockProcessor implements BlockProcessor { final BlockHeader blockHeader, final List transactions, final List ommers) { + final PrivacyGroupHeadBlockMap preProcessPrivacyGroupHeadBlockMap = + new PrivacyGroupHeadBlockMap( + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockHeader.getParentHash()) + .orElse(PrivacyGroupHeadBlockMap.EMPTY)); + transactions.stream() + .filter( + t -> + t.getTo().isPresent() + && t.getTo().equals(Optional.of(Address.ONCHAIN_PRIVACY)) + && t.getPayload().size() == 64) + .forEach( + t -> { + final Bytes32 addKey = Bytes32.wrap(t.getPayload().slice(32, 32)); + try { + final ReceiveResponse receiveResponse = enclave.receive(addKey.toBase64String()); + final List privateTransactionWithMetadataList = + deserializeAddToGroupPayload( + Bytes.wrap(Base64.getDecoder().decode(receiveResponse.getPayload()))); + final Bytes32 privacyGroupId = + Bytes32.wrap( + privateTransactionWithMetadataList + .get(0) + .getPrivateTransaction() + .getPrivacyGroupId() + .get()); + if (!preProcessPrivacyGroupHeadBlockMap.containsKey(privacyGroupId)) { + final PrivateStateRehydration privateStateRehydration = + new PrivateStateRehydration( + privateStateStorage, + blockchain, + protocolSchedule, + publicWorldStateArchive, + privateWorldStateArchive); + privateStateRehydration.rehydrate(privateTransactionWithMetadataList); + privateStateStorage.updater().putAddDataKey(privacyGroupId, addKey).commit(); + } + } catch (final EnclaveClientException e) { + // we were not being added + } + }); final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = new PrivacyGroupHeadBlockMap( privateStateStorage @@ -51,4 +121,17 @@ public class PrivacyBlockProcessor implements BlockProcessor { .commit(); return blockProcessor.processBlock(blockchain, worldState, blockHeader, transactions, ommers); } + + private List deserializeAddToGroupPayload( + final Bytes encodedAddToGroupPayload) { + final ArrayList deserializedResponse = new ArrayList<>(); + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput(encodedAddToGroupPayload, false); + final int noOfEntries = bytesValueRLPInput.enterList(); + for (int i = 0; i < noOfEntries; i++) { + deserializedResponse.add(PrivateTransactionWithMetadata.readFrom(bytesValueRLPInput)); + } + bytesValueRLPInput.leaveList(); + return deserializedResponse; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java index bcbb770e9..d733559e6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.math.BigInteger; import java.util.Optional; @@ -26,4 +27,7 @@ public interface ProtocolSchedule { Optional getChainId(); void setTransactionFilter(TransactionFilter transactionFilter); + + void setPublicWorldStateArchiveForPrivacyBlockProcessor( + WorldStateArchive publicWorldStateArchive); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java index 334aca077..eabc3694f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.vm.EVM; import org.hyperledger.besu.ethereum.vm.GasCalculator; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; /** A protocol specification. */ public class ProtocolSpec { @@ -288,4 +289,10 @@ public class ProtocolSpec { public void setTransactionFilter(final TransactionFilter transactionFilter) { transactionValidator.setTransactionFilter(transactionFilter); } + + public void setPublicWorldStateArchiveForPrivacyBlockProcessor( + final WorldStateArchive publicWorldStateArchive) { + if (PrivacyBlockProcessor.class.isAssignableFrom(blockProcessor.getClass())) + ((PrivacyBlockProcessor) blockProcessor).setPublicWorldStateArchive(publicWorldStateArchive); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 524491c62..7708a9f35 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.OnChainPrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.PrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; @@ -312,8 +313,17 @@ public class ProtocolSpecBuilder { (PrivacyPrecompiledContract) precompileContractRegistry.get(address, Account.DEFAULT_VERSION); privacyPrecompiledContract.setPrivateTransactionProcessor(privateTransactionProcessor); + final OnChainPrivacyPrecompiledContract onChainPrivacyPrecompiledContract = + (OnChainPrivacyPrecompiledContract) + precompileContractRegistry.get(Address.ONCHAIN_PRIVACY, Account.DEFAULT_VERSION); + onChainPrivacyPrecompiledContract.setPrivateTransactionProcessor(privateTransactionProcessor); blockProcessor = - new PrivacyBlockProcessor(blockProcessor, privacyParameters.getPrivateStateStorage()); + new PrivacyBlockProcessor( + blockProcessor, + protocolSchedule, + privacyParameters.getEnclave(), + privacyParameters.getPrivateStateStorage(), + privacyParameters.getPrivateWorldStateArchive()); } final BlockValidator blockValidator = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java new file mode 100644 index 000000000..9073bc84e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java @@ -0,0 +1,463 @@ +/* + * 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 org.hyperledger.besu.crypto.Hash.keccak256; +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.EnclaveIOException; +import org.hyperledger.besu.enclave.EnclaveServerException; +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.BlockHeader; +import org.hyperledger.besu.ethereum.core.DefaultEvmAccount; +import org.hyperledger.besu.ethereum.core.Gas; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.MutableAccount; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +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.mainnet.AbstractPrecompiledContract; +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.PrivateTransactionReceipt; +import org.hyperledger.besu.ethereum.privacy.Restriction; +import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction; +import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.RLP; +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.Base64; +import java.util.Optional; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class OnChainPrivacyPrecompiledContract extends AbstractPrecompiledContract { + + // Dummy signature for transactions to not fail being processed. + private static final SECP256K1.Signature FAKE_SIGNATURE = + SECP256K1.Signature.create(SECP256K1.HALF_CURVE_ORDER, SECP256K1.HALF_CURVE_ORDER, (byte) 0); + + private final Enclave enclave; + private final WorldStateArchive privateWorldStateArchive; + private final PrivateStateStorage privateStateStorage; + private final PrivateStateRootResolver privateStateRootResolver; + private PrivateTransactionProcessor privateTransactionProcessor; + + private static final Logger LOG = LogManager.getLogger(); + + public OnChainPrivacyPrecompiledContract( + final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) { + this( + gasCalculator, + privacyParameters.getEnclave(), + privacyParameters.getPrivateWorldStateArchive(), + privacyParameters.getPrivateStateStorage()); + } + + OnChainPrivacyPrecompiledContract( + final GasCalculator gasCalculator, + final Enclave enclave, + final WorldStateArchive worldStateArchive, + final PrivateStateStorage privateStateStorage) { + super("OnChainPrivacy", gasCalculator); + this.enclave = enclave; + this.privateWorldStateArchive = worldStateArchive; + this.privateStateStorage = privateStateStorage; + this.privateStateRootResolver = new PrivateStateRootResolver(privateStateStorage); + } + + public void setPrivateTransactionProcessor( + final PrivateTransactionProcessor privateTransactionProcessor) { + this.privateTransactionProcessor = privateTransactionProcessor; + } + + @Override + public Gas gasRequirement(final Bytes input) { + return Gas.of(0L); + } + + @Override + public Bytes compute(final Bytes input, final MessageFrame messageFrame) { + final ProcessableBlockHeader currentBlockHeader = messageFrame.getBlockHeader(); + if (!BlockHeader.class.isAssignableFrom(currentBlockHeader.getClass())) { + if (!messageFrame.isPersistingPrivateState()) { + // We get in here from block mining. + return Bytes.EMPTY; + } else { + throw new IllegalArgumentException( + "The MessageFrame contains an illegal block header type. Cannot persist private block metadata without current block hash."); + } + } + final Hash currentBlockHash = ((BlockHeader) currentBlockHeader).getHash(); + + final String key = input.slice(0, 32).toBase64String(); + + final ReceiveResponse receiveResponse; + try { + receiveResponse = enclave.receive(key); + } catch (final EnclaveClientException e) { + LOG.debug("Can not fetch private transaction payload with key {}", key, e); + return Bytes.EMPTY; + } catch (final EnclaveServerException e) { + LOG.error("Enclave is responding but errored 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; + } + + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput( + Bytes.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + final VersionedPrivateTransaction versionedPrivateTransaction = + VersionedPrivateTransaction.readFrom(bytesValueRLPInput); + final PrivateTransaction privateTransaction = + versionedPrivateTransaction.getPrivateTransaction(); + final Bytes32 version = versionedPrivateTransaction.getVersion(); + + final WorldUpdater publicWorldState = messageFrame.getWorldState(); + + final Optional maybeGroupId = privateTransaction.getPrivacyGroupId(); + if (maybeGroupId.isEmpty()) { + return Bytes.EMPTY; + } + + final Bytes32 privacyGroupId = Bytes32.wrap(maybeGroupId.get()); + + LOG.debug( + "Processing private transaction {} in privacy group {}", + privateTransaction.getHash(), + privacyGroupId); + + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).orElseThrow(); + + final Blockchain blockchain = messageFrame.getBlockchain(); + + final Hash lastRootHash = + privateStateRootResolver.resolveLastStateRoot(privacyGroupId, currentBlockHash); + + final MutableWorldState disposablePrivateState = + privateWorldStateArchive.getMutable(lastRootHash).get(); + + final WorldUpdater privateWorldStateUpdater = disposablePrivateState.updater(); + + maybeInjectDefaultManagementAndProxy( + lastRootHash, disposablePrivateState, privateWorldStateUpdater); + + final boolean isAddingParticipant = + privateTransaction + .getPayload() + .toHexString() + .startsWith(OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE.toHexString()); + + final boolean isPrivacyGroupLocked = + isContractLocked( + messageFrame, + currentBlockHeader, + publicWorldState, + privacyGroupId, + blockchain, + disposablePrivateState, + privateWorldStateUpdater); + + if (isAddingParticipant && !isPrivacyGroupLocked) { + LOG.debug( + "Privacy Group {} is not locked while trying to add to group with commitment {}", + privacyGroupId.toHexString(), + messageFrame.getTransactionHash()); + return Bytes.EMPTY; + } + + if (!isAddingParticipant && isPrivacyGroupLocked) { + LOG.debug( + "Privacy Group {} is locked while trying to execute transaction with commitment {}", + privacyGroupId.toHexString(), + messageFrame.getTransactionHash()); + return Bytes.EMPTY; + } + + if (!onChainPrivacyGroupVersionMatches( + messageFrame, + currentBlockHeader, + version, + publicWorldState, + privacyGroupId, + blockchain, + disposablePrivateState, + privateWorldStateUpdater)) return Bytes.EMPTY; + + final PrivateTransactionProcessor.Result result = + privateTransactionProcessor.processTransaction( + blockchain, + publicWorldState, + privateWorldStateUpdater, + currentBlockHeader, + privateTransaction, + messageFrame.getMiningBeneficiary(), + new DebugOperationTracer(TraceOptions.DEFAULT), + messageFrame.getBlockHashLookup(), + privacyGroupId); + + if (result.isInvalid() || !result.isSuccessful()) { + LOG.error( + "Failed to process private transaction {}: {}", + privateTransaction.getHash(), + result.getValidationResult().getErrorMessage()); + return Bytes.EMPTY; + } + + if (messageFrame.isPersistingPrivateState()) { + persistPrivateState( + messageFrame.getTransactionHash(), + currentBlockHash, + privateTransaction, + privacyGroupId, + privacyGroupHeadBlockMap, + disposablePrivateState, + privateWorldStateUpdater, + result); + } + + return result.getOutput(); + } + + protected boolean isContractLocked( + final MessageFrame messageFrame, + final ProcessableBlockHeader currentBlockHeader, + final WorldUpdater publicWorldState, + final Bytes32 privacyGroupId, + final Blockchain blockchain, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater) { + final PrivateTransactionProcessor.Result result = + checkCanExecute( + messageFrame, + currentBlockHeader, + publicWorldState, + privacyGroupId, + blockchain, + disposablePrivateState, + privateWorldStateUpdater, + OnChainGroupManagement.CAN_EXECUTE_METHOD_SIGNATURE); + return result.getOutput().toHexString().endsWith("0"); + } + + protected PrivateTransactionProcessor.Result checkCanExecute( + final MessageFrame messageFrame, + final ProcessableBlockHeader currentBlockHeader, + final WorldUpdater publicWorldState, + final Bytes32 privacyGroupId, + final Blockchain currentBlockchain, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater, + final Bytes canExecuteMethodSignature) { + // We need the "lock status" of the group for every single transaction but we don't want this + // call to affect the state + // privateTransactionProcessor.processTransaction(...) commits the state if the process was + // successful before it returns + final MutableWorldState canExecutePrivateState = + privateWorldStateArchive.getMutable(disposablePrivateState.rootHash()).get(); + final WorldUpdater canExecuteUpdater = canExecutePrivateState.updater(); + + return privateTransactionProcessor.processTransaction( + currentBlockchain, + publicWorldState, + canExecuteUpdater, + currentBlockHeader, + buildSimulationTransaction( + privacyGroupId, privateWorldStateUpdater, canExecuteMethodSignature), + messageFrame.getMiningBeneficiary(), + new DebugOperationTracer(TraceOptions.DEFAULT), + messageFrame.getBlockHashLookup(), + privacyGroupId); + } + + protected void maybeInjectDefaultManagementAndProxy( + final Hash lastRootHash, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater) { + if (lastRootHash.equals(EMPTY_ROOT_HASH)) { + // inject management + final DefaultEvmAccount managementPrecompile = + privateWorldStateUpdater.createAccount(Address.DEFAULT_PRIVACY_MANAGEMENT); + final MutableAccount mutableManagementPrecompiled = managementPrecompile.getMutable(); + // this is the code for the simple management contract + mutableManagementPrecompiled.setCode(OnChainGroupManagement.DEFAULT_GROUP_MANAGEMENT_CODE); + + // inject proxy + final DefaultEvmAccount proxyPrecompile = + privateWorldStateUpdater.createAccount(Address.PRIVACY_PROXY); + final MutableAccount mutableProxyPrecompiled = proxyPrecompile.getMutable(); + // this is the code for the proxy contract + mutableProxyPrecompiled.setCode(OnChainGroupManagement.DEFAULT_PROXY_PRECOMPILED_CODE); + // manually set the management contract address so the proxy can trust it + mutableProxyPrecompiled.setStorageValue( + UInt256.ZERO, UInt256.fromBytes(Bytes32.leftPad(Address.DEFAULT_PRIVACY_MANAGEMENT))); + + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(); + } + } + + protected boolean onChainPrivacyGroupVersionMatches( + final MessageFrame messageFrame, + final ProcessableBlockHeader currentBlockHeader, + final Bytes32 version, + final WorldUpdater publicWorldState, + final Bytes32 privacyGroupId, + final Blockchain currentBlockchain, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater) { + // We need the "version" of the group for every single transaction but we don't want this + // call to affect the state + // privateTransactionProcessor.processTransaction(...) commits the state if the process was + // successful before it returns + final PrivateTransactionProcessor.Result getVersionResult = + checkCanExecute( + messageFrame, + currentBlockHeader, + publicWorldState, + privacyGroupId, + currentBlockchain, + disposablePrivateState, + privateWorldStateUpdater, + OnChainGroupManagement.GET_VERSION_METHOD_SIGNATURE); + + if (version.equals(getVersionResult.getOutput())) { + return true; + } + LOG.debug( + "Privacy Group {} version mismatch for commitment {}: expecting {} but got {}", + privacyGroupId.toBase64String(), + messageFrame.getTransactionHash(), + getVersionResult.getOutput(), + version); + return false; + } + + protected void persistPrivateState( + final Hash commitmentHash, + final Hash currentBlockHash, + final PrivateTransaction privateTransaction, + final Bytes32 privacyGroupId, + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater, + final PrivateTransactionProcessor.Result result) { + + LOG.trace( + "Persisting private state {} for privacyGroup {}", + disposablePrivateState.rootHash(), + privacyGroupId); + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(); + + final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater(); + + updatePrivateBlockMetadata( + commitmentHash, + currentBlockHash, + privacyGroupId, + disposablePrivateState.rootHash(), + privateStateUpdater); + + final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); + + final int txStatus = + result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0; + + final PrivateTransactionReceipt privateTransactionReceipt = + new PrivateTransactionReceipt( + txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); + + privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt); + + if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) { + privacyGroupHeadBlockMap.put(Bytes32.wrap(privacyGroupId), currentBlockHash); + privateStateUpdater.putPrivacyGroupHeadBlockMap( + currentBlockHash, new PrivacyGroupHeadBlockMap(privacyGroupHeadBlockMap)); + } + + if (privateTransaction + .getPayload() + .toHexString() + .startsWith(OnChainGroupManagement.REMOVE_PARTICIPANT_METHOD_SIGNATURE.toHexString())) { + privacyGroupHeadBlockMap.remove(Bytes32.wrap(privacyGroupId)); + privateStateUpdater.putPrivacyGroupHeadBlockMap( + currentBlockHash, new PrivacyGroupHeadBlockMap(privacyGroupHeadBlockMap)); + } + privateStateUpdater.commit(); + } + + private PrivateTransaction buildSimulationTransaction( + final Bytes privacyGroupId, + final WorldUpdater privateWorldStateUpdater, + final Bytes payload) { + return PrivateTransaction.builder() + .privateFrom(Bytes.EMPTY) + .privacyGroupId(privacyGroupId) + .restriction(Restriction.RESTRICTED) + .nonce( + privateWorldStateUpdater.getAccount(Address.ZERO) != null + ? privateWorldStateUpdater.getAccount(Address.ZERO).getNonce() + : 0) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(Address.PRIVACY_PROXY) + .sender(Address.ZERO) + .value(Wei.ZERO) + .payload(payload) + .signature(FAKE_SIGNATURE) + .build(); + } + + private void updatePrivateBlockMetadata( + final Hash markerTransactionHash, + final Hash currentBlockHash, + final Bytes32 privacyGroupId, + final Hash rootHash, + final PrivateStateStorage.Updater privateStateUpdater) { + final PrivateBlockMetadata privateBlockMetadata = + privateStateStorage + .getPrivateBlockMetadata(currentBlockHash, Bytes32.wrap(privacyGroupId)) + .orElseGet(PrivateBlockMetadata::empty); + privateBlockMetadata.addPrivateTransactionMetadata( + new PrivateTransactionMetadata(markerTransactionHash, rootHash)); + privateStateUpdater.putPrivateBlockMetadata( + Bytes32.wrap(currentBlockHash), Bytes32.wrap(privacyGroupId), privateBlockMetadata); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index 23cc3e014..5cc1011bf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -126,7 +126,8 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { final BytesValueRLPInput bytesValueRLPInput = new BytesValueRLPInput( Bytes.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); - final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); + final PrivateTransaction privateTransaction = + PrivateTransaction.readFrom(bytesValueRLPInput.readAsRlp()); final WorldUpdater publicWorldState = messageFrame.getWorldState(); final Bytes32 privacyGroupId = Bytes32.wrap(Bytes.fromBase64String(receiveResponse.getPrivacyGroupId())); @@ -148,6 +149,7 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { privateWorldStateArchive.getMutable(lastRootHash).get(); final WorldUpdater privateWorldStateUpdater = disposablePrivateState.updater(); + final PrivateTransactionProcessor.Result result = privateTransactionProcessor.processTransaction( currentBlockchain, @@ -197,8 +199,6 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { privateStateUpdater.putTransactionReceipt( currentBlockHash, txHash, privateTransactionReceipt); - // TODO: this map could be passed through from @PrivacyBlockProcessor and saved once at the - // end of block processing if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) { privacyGroupHeadBlockMap.put(Bytes32.wrap(privacyGroupId), currentBlockHash); privateStateUpdater.putPrivacyGroupHeadBlockMap( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java index 2d0c5b409..e47fe0dee 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java @@ -14,22 +14,39 @@ */ 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.GET_PARTICIPANTS_METHOD_SIGNATURE; +import static org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement.GET_VERSION_METHOD_SIGNATURE; + import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.enclave.types.PrivacyGroup.Type; import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.privacy.markertransaction.PrivateMarkerTransactionFactory; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.transaction.CallParameter; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -40,11 +57,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; public class DefaultPrivacyController implements PrivacyController { private static final Logger LOG = LogManager.getLogger(); + private final Blockchain blockchain; + private final PrivateStateStorage privateStateStorage; private final Enclave enclave; private final PrivateTransactionValidator privateTransactionValidator; private final PrivateMarkerTransactionFactory privateMarkerTransactionFactory; @@ -53,6 +73,7 @@ public class DefaultPrivacyController implements PrivacyController { private final PrivateWorldStateReader privateWorldStateReader; public DefaultPrivacyController( + final Blockchain blockchain, final PrivacyParameters privacyParameters, final Optional chainId, final PrivateMarkerTransactionFactory privateMarkerTransactionFactory, @@ -60,6 +81,8 @@ public class DefaultPrivacyController implements PrivacyController { final PrivateNonceProvider privateNonceProvider, final PrivateWorldStateReader privateWorldStateReader) { this( + blockchain, + privacyParameters.getPrivateStateStorage(), privacyParameters.getEnclave(), new PrivateTransactionValidator(chainId), privateMarkerTransactionFactory, @@ -69,12 +92,16 @@ public class DefaultPrivacyController implements PrivacyController { } public DefaultPrivacyController( + final Blockchain blockchain, + final PrivateStateStorage privateStateStorage, final Enclave enclave, final PrivateTransactionValidator privateTransactionValidator, final PrivateMarkerTransactionFactory privateMarkerTransactionFactory, final PrivateTransactionSimulator privateTransactionSimulator, final PrivateNonceProvider privateNonceProvider, final PrivateWorldStateReader privateWorldStateReader) { + this.blockchain = blockchain; + this.privateStateStorage = privateStateStorage; this.enclave = enclave; this.privateTransactionValidator = privateTransactionValidator; this.privateMarkerTransactionFactory = privateMarkerTransactionFactory; @@ -128,6 +155,15 @@ public class DefaultPrivacyController implements PrivacyController { return privateMarkerTransactionFactory.create(transactionEnclaveKey, privateTransaction); } + @Override + public Transaction createPrivacyMarkerTransaction( + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Address privacyPrecompileAddress) { + return privateMarkerTransactionFactory.create( + transactionEnclaveKey, privateTransaction, privacyPrecompileAddress); + } + @Override public ValidationResult validatePrivateTransaction( final PrivateTransaction privateTransaction, final String enclavePublicKey) { @@ -185,6 +221,205 @@ public class DefaultPrivacyController implements PrivacyController { return result; } + @Override + public Optional buildAndSendAddPayload( + final PrivateTransaction privateTransaction, final String enclavePublicKey) { + if (isGroupAdditionTransaction(privateTransaction)) { + final List privateTransactionMetadataList = + buildTransactionMetadataList(Bytes32.wrap(privateTransaction.getPrivacyGroupId().get())); + if (privateTransactionMetadataList.size() > 0) { + final List privateTransactionWithMetadataList = + retrievePrivateTransactions( + Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()), + privateTransactionMetadataList, + enclavePublicKey); + final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList); + final List privateFor = + getParticipantsFromParameter(privateTransaction.getPayload()); + return Optional.of( + enclave.send(bytes.toBase64String(), enclavePublicKey, privateFor).getKey()); + } + } + return Optional.empty(); + } + + @Override + public PrivacyGroup retrievePrivacyGroup(final String privacyGroupId, final String enclaveKey) { + return enclave.retrievePrivacyGroup(privacyGroupId); + } + + @Override + public List findOnChainPrivacyGroup( + final List addresses, final String enclavePublicKey) { + final ArrayList privacyGroups = new ArrayList<>(); + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) + .orElse(PrivacyGroupHeadBlockMap.EMPTY); + privacyGroupHeadBlockMap + .keySet() + .forEach( + c -> { + final List participants = getExistingParticipants(c, enclavePublicKey); + if (participants.containsAll(addresses)) { + privacyGroups.add( + new PrivacyGroup(c.toBase64String(), Type.ONCHAIN, "", "", participants)); + } + }); + return privacyGroups; + } + + private List getExistingParticipants( + final Bytes privacyGroupId, final String enclavePublicKey) { + // get the privateFor list from the management contract + final Optional privateTransactionSimulatorResultOptional = + privateTransactionSimulator.process( + privacyGroupId.toBase64String(), + buildCallParams( + Bytes.fromBase64String(enclavePublicKey), GET_PARTICIPANTS_METHOD_SIGNATURE)); + + if (privateTransactionSimulatorResultOptional.isPresent() + && privateTransactionSimulatorResultOptional.get().isSuccessful()) { + final RLPInput rlpInput = + RLP.input(privateTransactionSimulatorResultOptional.get().getOutput()); + if (rlpInput.nextSize() > 0) { + return decodeList(rlpInput.raw()); + } else { + return Collections.emptyList(); + } + + } else { + // if the management contract does not exist this will prompt + // Orion to resolve the privateFor + return Collections.emptyList(); + } + } + + private List decodeList(final Bytes rlpEncodedList) { + final ArrayList decodedElements = new ArrayList<>(); + // first 32 bytes is dynamic list offset + final UInt256 lengthOfList = UInt256.fromBytes(rlpEncodedList.slice(32, 32)); // length of list + for (int i = 0; i < lengthOfList.toLong(); ++i) { + decodedElements.add( + Bytes.wrap(rlpEncodedList.slice(64 + (32 * i), 32)).toBase64String()); // participant + } + return decodedElements; + } + + private List getParticipantsFromParameter(final Bytes input) { + final List participants = new ArrayList<>(); + final Bytes mungedParticipants = input.slice(4 + 32 + 32 + 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) { + return new CallParameter( + Address.ZERO, + Address.PRIVACY_PROXY, + 3000000, + Wei.of(1000), + Wei.ZERO, + Bytes.concatenate(methodCall, enclavePublicKey)); + } + + private List buildTransactionMetadataList( + final Bytes32 privacyGroupId) { + final List pmtHashes = new ArrayList<>(); + PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) + .orElse(PrivacyGroupHeadBlockMap.EMPTY); + if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) { + Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); + while (blockHash != null) { + pmtHashes.addAll( + 0, + privateStateStorage + .getPrivateBlockMetadata(blockHash, privacyGroupId) + .get() + .getPrivateTransactionMetadataList()); + blockHash = blockchain.getBlockHeader(blockHash).get().getParentHash(); + privacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(blockHash) + .orElse(PrivacyGroupHeadBlockMap.EMPTY); + if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) { + blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); + } else { + break; + } + } + } + return pmtHashes; + } + + private List retrievePrivateTransactions( + final Bytes32 privacyGroupId, + final List privateTransactionMetadataList, + final String enclavePublicKey) { + final ArrayList privateTransactions = new ArrayList<>(); + privateStateStorage + .getAddDataKey(privacyGroupId) + .ifPresent(key -> privateTransactions.addAll(retrieveAddBlob(key.toBase64String()))); + for (int i = privateTransactions.size(); i < privateTransactionMetadataList.size(); i++) { + final PrivateTransactionMetadata privateTransactionMetadata = + privateTransactionMetadataList.get(i); + final Transaction privateMarkerTransaction = + blockchain + .getTransactionByHash(privateTransactionMetadata.getPrivacyMarkerTransactionHash()) + .orElseThrow(); + final ReceiveResponse receiveResponse = + retrieveTransaction( + privateMarkerTransaction.getPayload().slice(0, 32).toBase64String(), + enclavePublicKey); + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromBase64String(new String(receiveResponse.getPayload(), UTF_8)), false); + input.enterList(); + privateTransactions.add( + new PrivateTransactionWithMetadata( + PrivateTransaction.readFrom(input), privateTransactionMetadata)); + input.leaveListLenient(); + } + + return privateTransactions; + } + + @Override + public List retrieveAddBlob(final String addDataKey) { + final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey); + return deserializeAddToGroupPayload( + Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload()))); + } + + private Bytes serializeAddToGroupPayload( + final List privateTransactionWithMetadataList) { + + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + privateTransactionWithMetadataList.forEach( + privateTransactionWithMetadata -> privateTransactionWithMetadata.writeTo(rlpOutput)); + rlpOutput.endList(); + + return rlpOutput.encoded(); + } + + private List deserializeAddToGroupPayload( + final Bytes encodedAddToGroupPayload) { + final ArrayList deserializedResponse = new ArrayList<>(); + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput(encodedAddToGroupPayload, false); + final int noOfEntries = bytesValueRLPInput.enterList(); + for (int i = 0; i < noOfEntries; i++) { + deserializedResponse.add(PrivateTransactionWithMetadata.readFrom(bytesValueRLPInput)); + } + bytesValueRLPInput.leaveList(); + return deserializedResponse; + } + @Override public Optional getContractCode( final String privacyGroupId, @@ -197,23 +432,75 @@ public class DefaultPrivacyController implements PrivacyController { private SendResponse sendRequest( final PrivateTransaction privateTransaction, final String enclavePublicKey) { final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + + final List privateFor = resolvePrivateFor(privateTransaction, enclavePublicKey); + + if (privateTransaction.getPrivacyGroupId().isPresent()) { + PrivacyGroup privacyGroup; + try { + privacyGroup = + enclave.retrievePrivacyGroup( + privateTransaction.getPrivacyGroupId().get().toBase64String()); + } catch (final EnclaveClientException e) { + // onchain privacy group + final Optional result = + privateTransactionSimulator.process( + privateTransaction.getPrivacyGroupId().get().toBase64String(), + buildCallParams( + Bytes.fromBase64String(enclavePublicKey), GET_VERSION_METHOD_SIGNATURE)); + new VersionedPrivateTransaction(privateTransaction, result).writeTo(rlpOutput); + if (privateFor.isEmpty()) { + privateFor.add(privateTransaction.getPrivateFrom().toBase64String()); + } + return enclave.send( + rlpOutput.encoded().toBase64String(), + privateTransaction.getPrivateFrom().toBase64String(), + privateFor); + } + if (privacyGroup != null) { + privateTransaction.writeTo(rlpOutput); + return enclave.send( + rlpOutput.encoded().toBase64String(), + privateTransaction.getPrivateFrom().toBase64String(), + privateTransaction.getPrivacyGroupId().get().toBase64String()); + } + } + if (privateFor.isEmpty()) { + privateFor.add(privateTransaction.getPrivateFrom().toBase64String()); + } + privateTransaction.writeTo(rlpOutput); final String payload = rlpOutput.encoded().toBase64String(); - if (privateTransaction.getPrivacyGroupId().isPresent()) { - return enclave.send( - payload, enclavePublicKey, privateTransaction.getPrivacyGroupId().get().toBase64String()); - } else { - final List privateFor = + return enclave.send(payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor); + } + + private List resolvePrivateFor( + final PrivateTransaction privateTransaction, final String enclavePublicKey) { + final ArrayList privateFor = new ArrayList<>(); + final boolean isLegacyTransaction = privateTransaction.getPrivateFor().isPresent(); + if (isLegacyTransaction) { + privateFor.addAll( privateTransaction.getPrivateFor().get().stream() .map(Bytes::toBase64String) - .collect(Collectors.toList()); - - if (privateFor.isEmpty()) { - privateFor.add(privateTransaction.getPrivateFrom().toBase64String()); - } - return enclave.send( - payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor); + .collect(Collectors.toList())); + } else if (isGroupAdditionTransaction(privateTransaction)) { + privateFor.addAll(getParticipantsFromParameter(privateTransaction.getPayload())); + privateFor.addAll( + getExistingParticipants(privateTransaction.getPrivacyGroupId().get(), enclavePublicKey)); + } else { + privateFor.addAll( + getExistingParticipants(privateTransaction.getPrivacyGroupId().get(), enclavePublicKey)); } + return privateFor; + } + + private boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { + return privateTransaction.getTo().isPresent() + && privateTransaction.getTo().get().equals(Address.PRIVACY_PROXY) + && privateTransaction + .getPayload() + .toHexString() + .startsWith(ADD_TO_GROUP_METHOD_SIGNATURE.toHexString()); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java index 76c7ae58a..cb22bb279 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInv import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -83,7 +84,11 @@ public class MultiTenancyPrivacyController implements PrivacyController { throw new MultiTenancyValidationException( "Privacy group addresses must contain the enclave public key"); } - return privacyController.findPrivacyGroup(addresses, enclavePublicKey); + PrivacyGroup[] resultantGroups = + privacyController.findPrivacyGroup(addresses, enclavePublicKey); + return Arrays.stream(resultantGroups) + .filter(g -> g.getMembers().contains(enclavePublicKey)) + .toArray(PrivacyGroup[]::new); } @Override @@ -93,6 +98,15 @@ public class MultiTenancyPrivacyController implements PrivacyController { transactionEnclaveKey, privateTransaction); } + @Override + public Transaction createPrivacyMarkerTransaction( + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Address privacyPrecompileAddress) { + return privacyController.createPrivacyMarkerTransaction( + transactionEnclaveKey, privateTransaction, privacyPrecompileAddress); + } + @Override public ValidationResult validatePrivateTransaction( final PrivateTransaction privateTransaction, final String enclavePublicKey) { @@ -127,6 +141,39 @@ public class MultiTenancyPrivacyController implements PrivacyController { privacyGroupId, enclavePublicKey, callParams, blockNumber); } + @Override + public Optional buildAndSendAddPayload( + final PrivateTransaction privateTransaction, final String enclaveKey) { + verifyPrivateFromMatchesEnclavePublicKey( + privateTransaction.getPrivateFrom().toBase64String(), enclaveKey); + verifyPrivacyGroupContainsEnclavePublicKey( + privateTransaction.getPrivacyGroupId().get().toBase64String(), enclaveKey); + return privacyController.buildAndSendAddPayload(privateTransaction, enclaveKey); + } + + @Override + public PrivacyGroup retrievePrivacyGroup( + final String privacyGroupId, final String enclavePublicKey) { + final PrivacyGroup privacyGroup = + privacyController.retrievePrivacyGroup(privacyGroupId, enclavePublicKey); + if (!privacyGroup.getMembers().contains(enclavePublicKey)) { + throw new MultiTenancyValidationException( + "Privacy group must contain the enclave public key"); + } + return privacyGroup; + } + + @Override + public List findOnChainPrivacyGroup( + final List asList, final String enclaveKey) { + return privacyController.findOnChainPrivacyGroup(asList, enclaveKey); + } + + @Override + public List retrieveAddBlob(final String addDataKey) { + return privacyController.retrieveAddBlob(addDataKey); + } + @Override public Optional getContractCode( final String privacyGroupId, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java index 7e12c2f20..521e89a19 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivacyController.java @@ -44,6 +44,11 @@ public interface PrivacyController { Transaction createPrivacyMarkerTransaction( String transactionEnclaveKey, PrivateTransaction privateTransaction); + Transaction createPrivacyMarkerTransaction( + String transactionEnclaveKey, + PrivateTransaction privateTransaction, + Address privacyPrecompileAddress); + ValidationResult validatePrivateTransaction( PrivateTransaction privateTransaction, String enclavePublicKey); @@ -58,9 +63,17 @@ public interface PrivacyController { final CallParameter callParams, final long blockNumber); + Optional buildAndSendAddPayload(PrivateTransaction privateTransaction, String enclaveKey); + + PrivacyGroup retrievePrivacyGroup(String toBase64String, String enclaveKey); + + List findOnChainPrivacyGroup(List asList, String enclaveKey); + Optional getContractCode( final String privacyGroupId, final Address contractAddress, final Hash blockHash, final String enclavePublicKey); + + List retrieveAddBlob(String addDataKey); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java new file mode 100644 index 000000000..a8c981a7f --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java @@ -0,0 +1,308 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy; + +import static org.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH; + +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.BlockHeader; +import org.hyperledger.besu.ethereum.core.DefaultEvmAccount; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.MutableAccount; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.core.WorldUpdater; +import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; +import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; +import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.vm.BlockHashLookup; +import org.hyperledger.besu.ethereum.vm.OperationTracer; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class PrivateGroupRehydrationBlockProcessor { + + private static final Logger LOG = LogManager.getLogger(); + + static final int MAX_GENERATION = 6; + + private final TransactionProcessor transactionProcessor; + private final PrivateTransactionProcessor privateTransactionProcessor; + private final MainnetBlockProcessor.TransactionReceiptFactory transactionReceiptFactory; + final Wei blockReward; + private final boolean skipZeroBlockRewards; + private final MiningBeneficiaryCalculator miningBeneficiaryCalculator; + + public PrivateGroupRehydrationBlockProcessor( + final TransactionProcessor transactionProcessor, + final PrivateTransactionProcessor privateTransactionProcessor, + final MainnetBlockProcessor.TransactionReceiptFactory transactionReceiptFactory, + final Wei blockReward, + final MiningBeneficiaryCalculator miningBeneficiaryCalculator, + final boolean skipZeroBlockRewards) { + this.transactionProcessor = transactionProcessor; + this.privateTransactionProcessor = privateTransactionProcessor; + this.transactionReceiptFactory = transactionReceiptFactory; + this.blockReward = blockReward; + this.miningBeneficiaryCalculator = miningBeneficiaryCalculator; + this.skipZeroBlockRewards = skipZeroBlockRewards; + } + + public AbstractBlockProcessor.Result processBlock( + final Blockchain blockchain, + final MutableWorldState worldState, + final WorldStateArchive privateWorldStateArchive, + final PrivateStateStorage privateStateStorage, + final Block block, + final Map forExecution, + final List ommers) { + long gasUsed = 0; + final List receipts = new ArrayList<>(); + + final List transactions = block.getBody().getTransactions(); + final BlockHeader blockHeader = block.getHeader(); + for (final Transaction transaction : transactions) { + + final long remainingGasBudget = blockHeader.getGasLimit() - gasUsed; + if (Long.compareUnsigned(transaction.getGasLimit(), remainingGasBudget) > 0) { + LOG.warn( + "Transaction processing error: transaction gas limit {} exceeds available block budget remaining {}", + transaction.getGasLimit(), + remainingGasBudget); + return AbstractBlockProcessor.Result.failed(); + } + + final WorldUpdater worldStateUpdater = worldState.updater(); + final BlockHashLookup blockHashLookup = new BlockHashLookup(blockHeader, blockchain); + final Address miningBeneficiary = + miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); + + final PrivateStateRootResolver privateStateRootResolver = + new PrivateStateRootResolver(privateStateStorage); + if (forExecution.containsKey(transaction.getHash())) { + final PrivateTransaction privateTransaction = forExecution.get(transaction.getHash()); + final Hash lastRootHash = + privateStateRootResolver.resolveLastStateRoot( + Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()), + blockHeader.getParentHash()); + + final MutableWorldState disposablePrivateState = + privateWorldStateArchive.getMutable(lastRootHash).get(); + final WorldUpdater privateStateUpdater = disposablePrivateState.updater(); + maybeInjectDefaultManagementAndProxy( + lastRootHash, disposablePrivateState, privateStateUpdater); + LOG.debug( + "Pre-rehydrate root hash: {} for tx {}", + disposablePrivateState.rootHash(), + privateTransaction.getHash()); + + final PrivateTransactionProcessor.Result privateResult = + privateTransactionProcessor.processTransaction( + blockchain, + worldStateUpdater.updater(), + privateStateUpdater, + blockHeader, + privateTransaction, + miningBeneficiary, + OperationTracer.NO_TRACING, + new BlockHashLookup(blockHeader, blockchain), + privateTransaction.getPrivacyGroupId().get()); + persistPrivateState( + transaction.getHash(), + blockHeader.getHash(), + privateTransaction, + Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()), + disposablePrivateState, + privateStateUpdater, + privateStateStorage, + privateResult); + LOG.debug("Post-rehydrate root hash: {}", disposablePrivateState.rootHash()); + } + + final TransactionProcessor.Result result = + transactionProcessor.processTransaction( + blockchain, + worldStateUpdater, + blockHeader, + transaction, + miningBeneficiary, + blockHashLookup, + false, + TransactionValidationParams.processingBlock()); + if (result.isInvalid()) { + return AbstractBlockProcessor.Result.failed(); + } + + worldStateUpdater.commit(); + gasUsed = transaction.getGasLimit() - result.getGasRemaining() + gasUsed; + final TransactionReceipt transactionReceipt = + transactionReceiptFactory.create(result, worldState, gasUsed); + receipts.add(transactionReceipt); + } + + if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { + return AbstractBlockProcessor.Result.failed(); + } + + return AbstractBlockProcessor.Result.successful(receipts); + } + + protected void persistPrivateState( + final Hash commitmentHash, + final Hash currentBlockHash, + final PrivateTransaction privateTransaction, + final Bytes32 privacyGroupId, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater, + final PrivateStateStorage privateStateStorage, + final PrivateTransactionProcessor.Result result) { + + LOG.trace( + "Persisting private state {} for privacyGroup {}", + disposablePrivateState.rootHash(), + privacyGroupId); + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(); + + final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater(); + + updatePrivateBlockMetadata( + commitmentHash, + currentBlockHash, + privacyGroupId, + disposablePrivateState.rootHash(), + privateStateUpdater, + privateStateStorage); + + final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); + + final int txStatus = + result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0; + + final PrivateTransactionReceipt privateTransactionReceipt = + new PrivateTransactionReceipt( + txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); + + privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt); + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).get(); + if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) { + privacyGroupHeadBlockMap.put(Bytes32.wrap(privacyGroupId), currentBlockHash); + privateStateUpdater.putPrivacyGroupHeadBlockMap( + currentBlockHash, new PrivacyGroupHeadBlockMap(privacyGroupHeadBlockMap)); + } + privateStateUpdater.commit(); + } + + protected void maybeInjectDefaultManagementAndProxy( + final Hash lastRootHash, + final MutableWorldState disposablePrivateState, + final WorldUpdater privateWorldStateUpdater) { + if (lastRootHash.equals(EMPTY_ROOT_HASH)) { + // inject management + final DefaultEvmAccount managementPrecompile = + privateWorldStateUpdater.createAccount(Address.DEFAULT_PRIVACY_MANAGEMENT); + final MutableAccount mutableManagementPrecompiled = managementPrecompile.getMutable(); + // this is the code for the simple management contract + mutableManagementPrecompiled.setCode(OnChainGroupManagement.DEFAULT_GROUP_MANAGEMENT_CODE); + + // inject proxy + final DefaultEvmAccount proxyPrecompile = + privateWorldStateUpdater.createAccount(Address.PRIVACY_PROXY); + final MutableAccount mutableProxyPrecompiled = proxyPrecompile.getMutable(); + // this is the code for the proxy contract + mutableProxyPrecompiled.setCode(OnChainGroupManagement.DEFAULT_PROXY_PRECOMPILED_CODE); + // manually set the management contract address so the proxy can trust it + mutableProxyPrecompiled.setStorageValue( + UInt256.ZERO, UInt256.fromBytes(Bytes32.leftPad(Address.DEFAULT_PRIVACY_MANAGEMENT))); + + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(); + } + } + + private void updatePrivateBlockMetadata( + final Hash markerTransactionHash, + final Hash currentBlockHash, + final Bytes32 privacyGroupId, + final Hash rootHash, + final PrivateStateStorage.Updater privateStateUpdater, + final PrivateStateStorage privateStateStorage) { + final PrivateBlockMetadata privateBlockMetadata = + privateStateStorage + .getPrivateBlockMetadata(currentBlockHash, Bytes32.wrap(privacyGroupId)) + .orElseGet(PrivateBlockMetadata::empty); + privateBlockMetadata.addPrivateTransactionMetadata( + new PrivateTransactionMetadata(markerTransactionHash, rootHash)); + privateStateUpdater.putPrivateBlockMetadata( + Bytes32.wrap(currentBlockHash), Bytes32.wrap(privacyGroupId), privateBlockMetadata); + } + + private boolean rewardCoinbase( + final MutableWorldState worldState, + final ProcessableBlockHeader header, + final List ommers, + final boolean skipZeroBlockRewards) { + if (skipZeroBlockRewards && blockReward.isZero()) { + return true; + } + + final Wei coinbaseReward = blockReward.add(blockReward.multiply(ommers.size()).divide(32)); + final WorldUpdater updater = worldState.updater(); + final MutableAccount coinbase = updater.getOrCreate(header.getCoinbase()).getMutable(); + + coinbase.incrementBalance(coinbaseReward); + for (final BlockHeader ommerHeader : ommers) { + if (ommerHeader.getNumber() - header.getNumber() > MAX_GENERATION) { + LOG.warn( + "Block processing error: ommer block number {} more than {} generations current block number {}", + ommerHeader.getNumber(), + MAX_GENERATION, + header.getNumber()); + return false; + } + + final MutableAccount ommerCoinbase = + updater.getOrCreate(ommerHeader.getCoinbase()).getMutable(); + final long distance = header.getNumber() - ommerHeader.getNumber(); + final Wei ommerReward = blockReward.subtract(blockReward.multiply(distance).divide(8)); + ommerCoinbase.incrementBalance(ommerReward); + } + + return true; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java new file mode 100644 index 000000000..4a9351b69 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java @@ -0,0 +1,246 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.TransactionLocation; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class PrivateStateRehydration { + + private static final Logger LOG = LogManager.getLogger(); + + private final PrivateStateStorage privateStateStorage; + private final Blockchain blockchain; + private final ProtocolSchedule protocolSchedule; + private final WorldStateArchive publicWorldStateArchive; + private final WorldStateArchive privateWorldStateArchive; + + public PrivateStateRehydration( + final PrivateStateStorage privateStateStorage, + final Blockchain blockchain, + final ProtocolSchedule protocolSchedule, + final WorldStateArchive publicWorldStateArchive, + final WorldStateArchive privateWorldStateArchive) { + this.privateStateStorage = privateStateStorage; + this.blockchain = blockchain; + this.protocolSchedule = protocolSchedule; + this.publicWorldStateArchive = publicWorldStateArchive; + this.privateWorldStateArchive = privateWorldStateArchive; + } + + public void rehydrate( + final List privateTransactionWithMetadataList) { + final long rehydrationStartTimestamp = System.currentTimeMillis(); + final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber(); + final Optional maybeGroupId = + privateTransactionWithMetadataList.get(0).getPrivateTransaction().getPrivacyGroupId(); + if (maybeGroupId.isEmpty()) { + LOG.debug("On-chain groups must have a group id."); + return; + } + final Bytes32 privacyGroupId = Bytes32.wrap(maybeGroupId.get()); + + LOG.debug("Rehydrating privacy group {}", privacyGroupId.toBase64String()); + + // check if there is a privacyGroupHeadBlockMap for the first block ... + final boolean needEmptyPrivacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap( + getBlockHashForIndex(0, privateTransactionWithMetadataList)) + .isEmpty(); + if (needEmptyPrivacyGroupHeadBlockMap) { + privateStateStorage + .updater() + .putPrivacyGroupHeadBlockMap( + getBlockHashForIndex(0, privateTransactionWithMetadataList), + PrivacyGroupHeadBlockMap.EMPTY) + .commit(); + } + + for (int i = 0; i < privateTransactionWithMetadataList.size(); i++) { + // find out which block this transaction is in + final Hash blockHash = getBlockHashForIndex(i, privateTransactionWithMetadataList); + + // if there are multiple pmts in the list we can increment our index i. At the end of the + // while loop i will be the index of the last PMT (for this group) that is in this block. + while (i + 1 < privateTransactionWithMetadataList.size() + && blockHash.equals(getBlockHashForIndex(i + 1, privateTransactionWithMetadataList))) { + i++; + } + + final Hash lastPmtHash = + privateTransactionWithMetadataList + .get(i) + .getPrivateTransactionMetadata() + .getPrivacyMarkerTransactionHash(); + + final Optional transactionLocationOfLastPmtInBlock = + blockchain.getTransactionLocation(lastPmtHash); + if (transactionLocationOfLastPmtInBlock.isEmpty()) { + LOG.debug("Rehydartion failed - missing marker transaction for {}", lastPmtHash); + return; + } + + final Block block = blockchain.getBlockByHash(blockHash).orElseThrow(RuntimeException::new); + final BlockHeader blockHeader = block.getHeader(); + LOG.debug( + "Rehydrating block {} ({}/{}), {}", + blockHash, + blockHeader.getNumber(), + chainHeadBlockNumber, + block.getBody().getTransactions().stream() + .map(Transaction::getHash) + .collect(Collectors.toList())); + + final ProtocolSpec protocolSpec = + protocolSchedule.getByBlockNumber(blockchain.getBlockHeader(blockHash).get().getNumber()); + final PrivateGroupRehydrationBlockProcessor privateGroupRehydrationBlockProcessor = + new PrivateGroupRehydrationBlockProcessor( + protocolSpec.getTransactionProcessor(), + protocolSpec.getPrivateTransactionProcessor(), + protocolSpec.getTransactionReceiptFactory(), + protocolSpec.getBlockReward(), + protocolSpec.getMiningBeneficiaryCalculator(), + protocolSpec.isSkipZeroBlockRewards()); + + final MutableWorldState publicWorldState = + blockchain + .getBlockHeader(blockHeader.getParentHash()) + .map(BlockHeader::getStateRoot) + .flatMap(publicWorldStateArchive::getMutable) + .orElseThrow(RuntimeException::new); + + // enclave cache for private block rehydration + final LinkedHashMap enclaveMap = new LinkedHashMap<>(); + for (int j = 0; j < privateTransactionWithMetadataList.size(); j++) { + final PrivateTransactionWithMetadata transactionWithMetadata = + privateTransactionWithMetadataList.get(j); + enclaveMap.put( + transactionWithMetadata + .getPrivateTransactionMetadata() + .getPrivacyMarkerTransactionHash(), + transactionWithMetadata.getPrivateTransaction()); + } + + privateGroupRehydrationBlockProcessor.processBlock( + blockchain, + publicWorldState, + privateWorldStateArchive, + privateStateStorage, + block, + enclaveMap, + block.getBody().getOmmers()); + + // check the resulting private state against the state in the meta data + final Optional latestStateRoot = + privateStateStorage + .getPrivateBlockMetadata(blockHash, privacyGroupId) + .orElseThrow() + .getLatestStateRoot(); + if (latestStateRoot.isPresent()) { + if (!latestStateRoot + .get() + .equals( + privateTransactionWithMetadataList + .get(i) + .getPrivateTransactionMetadata() + .getStateRoot())) { + throw new RuntimeException(); + } + } + // fix the privacy group header block map for the blocks between the current block and the + // next block containing a pmt for this privacy group + if (i + 1 < privateTransactionWithMetadataList.size()) { + rehydratePrivacyGroupHeadBlockMap( + privacyGroupId, + blockHash, + blockchain, + getBlockNumberForIndex(i, privateTransactionWithMetadataList), + getBlockNumberForIndex(i + 1, privateTransactionWithMetadataList)); + } else { + rehydratePrivacyGroupHeadBlockMap( + privacyGroupId, + blockHash, + blockchain, + getBlockNumberForIndex(i, privateTransactionWithMetadataList), + blockchain.getChainHeadBlockNumber() + 1); + } + } + final long rehydrationDuration = System.currentTimeMillis() - rehydrationStartTimestamp; + LOG.debug("Rehydration took {} seconds", rehydrationDuration / 1000.0); + } + + protected void rehydratePrivacyGroupHeadBlockMap( + final Bytes32 privacyGroupId, + final Hash hashOfLastBlockWithPmt, + final Blockchain currentBlockchain, + final long from, + final long to) { + for (long j = from + 1; j < to; j++) { + final BlockHeader theBlockHeader = currentBlockchain.getBlockHeader(j).orElseThrow(); + final PrivacyGroupHeadBlockMap thePrivacyGroupHeadBlockMap = + privateStateStorage + .getPrivacyGroupHeadBlockMap(theBlockHeader.getHash()) + .orElse(PrivacyGroupHeadBlockMap.EMPTY); + final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater(); + thePrivacyGroupHeadBlockMap.put(privacyGroupId, hashOfLastBlockWithPmt); + privateStateUpdater.putPrivacyGroupHeadBlockMap( + theBlockHeader.getHash(), new PrivacyGroupHeadBlockMap(thePrivacyGroupHeadBlockMap)); + privateStateUpdater.commit(); + } + } + + private long getBlockNumberForIndex( + final int index, + final List privateTransactionWithMetadataList) { + return blockchain + .getBlockHeader(getBlockHashForIndex(index, privateTransactionWithMetadataList)) + .orElseThrow() + .getNumber(); + } + + private Hash getBlockHashForIndex( + final int index, + final List privateTransactionWithMetadataList) { + return blockchain + .getTransactionLocation( + privateTransactionWithMetadataList + .get(index) + .getPrivateTransactionMetadata() + .getPrivacyMarkerTransactionHash()) + .orElseThrow() + .getBlockHash(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java index a35955658..787114800 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java @@ -34,7 +34,7 @@ import org.apache.tuweni.bytes.Bytes; public class PrivateTransactionReceipt { @SuppressWarnings("unchecked") - public static final PrivateTransactionReceipt EMPTY = + public static final PrivateTransactionReceipt FAILED = new PrivateTransactionReceipt( 0, Collections.EMPTY_LIST, Bytes.EMPTY, Optional.ofNullable(null)); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java index 2211f37e5..2ea2f6f76 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionSimulator.java @@ -70,6 +70,12 @@ public class PrivateTransactionSimulator { new PrivateStateRootResolver(privacyParameters.getPrivateStateStorage()); } + public Optional process( + final String privacyGroupId, final CallParameter callParams) { + final BlockHeader header = blockchain.getChainHeadHeader(); + return process(privacyGroupId, callParams, header); + } + public Optional process( final String privacyGroupId, final CallParameter callParams, final Hash blockHeaderHash) { final BlockHeader header = blockchain.getBlockHeader(blockHeaderHash).orElse(null); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionWithMetadata.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionWithMetadata.java new file mode 100644 index 000000000..74afc2306 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionWithMetadata.java @@ -0,0 +1,72 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy; + +import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.util.Objects; + +public class PrivateTransactionWithMetadata { + private final PrivateTransaction privateTransaction; + private final PrivateTransactionMetadata privateTransactionMetadata; + + public static PrivateTransactionWithMetadata readFrom(final RLPInput input) throws RLPException { + input.enterList(); + final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(input.readAsRlp()); + final PrivateTransactionMetadata privateTransactionMetadata = + PrivateTransactionMetadata.readFrom(input.readAsRlp()); + input.leaveList(); + return new PrivateTransactionWithMetadata(privateTransaction, privateTransactionMetadata); + } + + public PrivateTransactionWithMetadata( + final PrivateTransaction privateTransaction, + final PrivateTransactionMetadata privateTransactionMetadata) { + this.privateTransaction = privateTransaction; + this.privateTransactionMetadata = privateTransactionMetadata; + } + + public void writeTo(final RLPOutput out) { + out.startList(); + privateTransaction.writeTo(out); + privateTransactionMetadata.writeTo(out); + out.endList(); + } + + public PrivateTransaction getPrivateTransaction() { + return privateTransaction; + } + + public PrivateTransactionMetadata getPrivateTransactionMetadata() { + return privateTransactionMetadata; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final PrivateTransactionWithMetadata that = (PrivateTransactionWithMetadata) o; + return privateTransaction.equals(that.privateTransaction) + && privateTransactionMetadata.equals(that.privateTransactionMetadata); + } + + @Override + public int hashCode() { + return Objects.hash(privateTransaction, privateTransactionMetadata); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/VersionedPrivateTransaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/VersionedPrivateTransaction.java new file mode 100644 index 000000000..62c8912d7 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/VersionedPrivateTransaction.java @@ -0,0 +1,67 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy; + +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; + +public class VersionedPrivateTransaction { + private final PrivateTransaction privateTransaction; + private final Bytes32 version; + + public VersionedPrivateTransaction( + final PrivateTransaction privateTransaction, + final Optional result) { + this( + privateTransaction, + result + .map(value -> Bytes32.fromHexStringLenient(value.getOutput().toHexString())) + .orElse(Bytes32.ZERO)); + } + + public VersionedPrivateTransaction( + final PrivateTransaction privateTransaction, final Bytes32 version) { + this.privateTransaction = privateTransaction; + this.version = version; + } + + public static VersionedPrivateTransaction readFrom(final RLPInput input) throws RLPException { + input.enterList(); + final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(input.readAsRlp()); + final Bytes32 version = input.readBytes32(); + input.leaveList(); + return new VersionedPrivateTransaction(privateTransaction, version); + } + + public PrivateTransaction getPrivateTransaction() { + return privateTransaction; + } + + public Bytes32 getVersion() { + return version; + } + + public void writeTo(final BytesValueRLPOutput rlpOutput) { + rlpOutput.startList(); + privateTransaction.writeTo(rlpOutput); + rlpOutput.writeBytes(version); + rlpOutput.endList(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/group/OnChainGroupManagement.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/group/OnChainGroupManagement.java new file mode 100644 index 000000000..b2da1777f --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/group/OnChainGroupManagement.java @@ -0,0 +1,34 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy.group; + +import org.apache.tuweni.bytes.Bytes; + +public class OnChainGroupManagement { + public static final Bytes DEFAULT_PROXY_PRECOMPILED_CODE = + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50600436106100935760003560e01c806361544c911161006657806361544c91146101c757806378b9033714610217578063a69df4b514610239578063f744b08914610243578063f83d08ba1461031d57610093565b80630b0235be146100985780630d8e6e2c1461011b5780633659cfe6146101395780635c60da1b1461017d575b600080fd5b6100c4600480360360208110156100ae57600080fd5b8101908080359060200190929190505050610327565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156101075780820151818401526020810190506100ec565b505050509050019250505060405180910390f35b61012361047d565b6040518082815260200191505060405180910390f35b61017b6004803603602081101561014f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061052b565b005b610185610591565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101fd600480360360408110156101dd57600080fd5b8101908080359060200190929190803590602001909291905050506105b6565b604051808215151515815260200191505060405180910390f35b61021f61067c565b604051808215151515815260200191505060405180910390f35b61024161072a565b005b6103036004803603604081101561025957600080fd5b81019080803590602001909291908035906020019064010000000081111561028057600080fd5b82018360208201111561029257600080fd5b803590602001918460208302840111640100000000831117156102b457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506107b3565b604051808215151515815260200191505060405180910390f35b6103256108ba565b005b606060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff16630b0235be846040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b1580156103a057600080fd5b505afa1580156103b4573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525060208110156103de57600080fd5b81019080805160405193929190846401000000008211156103fe57600080fd5b8382019150602082018581111561041457600080fd5b825186602082028301116401000000008211171561043157600080fd5b8083526020830192505050908051906020019060200280838360005b8381101561046857808201518184015260208101905061044d565b50505050905001604052505050915050919050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff16630d8e6e2c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156104ea57600080fd5b505afa1580156104fe573d6000803e3d6000fd5b505050506040513d602081101561051457600080fd5b810190808051906020019092919050505091505090565b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561058557600080fd5b61058e81610943565b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff166361544c9185856040518363ffffffff1660e01b81526004018083815260200182815260200192505050602060405180830381600087803b15801561063857600080fd5b505af115801561064c573d6000803e3d6000fd5b505050506040513d602081101561066257600080fd5b810190808051906020019092919050505091505092915050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff166378b903376040518163ffffffff1660e01b815260040160206040518083038186803b1580156106e957600080fd5b505afa1580156106fd573d6000803e3d6000fd5b505050506040513d602081101561071357600080fd5b810190808051906020019092919050505091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663a69df4b56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561079857600080fd5b505af11580156107ac573d6000803e3d6000fd5b5050505050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663f744b08985856040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b83811015610850578082015181840152602081019050610835565b505050509050019350505050602060405180830381600087803b15801561087657600080fd5b505af115801561088a573d6000803e3d6000fd5b505050506040513d60208110156108a057600080fd5b810190808051906020019092919050505091505092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663f83d08ba6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561092857600080fd5b505af115801561093c573d6000803e3d6000fd5b5050505050565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820498cff5aa36efa4596466bc5546201e9c41df019fcc994f209e16b330d34284b64736f6c63430005100032"); + + public static final Bytes DEFAULT_GROUP_MANAGEMENT_CODE = + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c806378b903371161005b57806378b9033714610173578063a69df4b514610195578063f744b0891461019f578063f83d08ba146102795761007d565b80630b0235be146100825780630d8e6e2c1461010557806361544c9114610123575b600080fd5b6100ae6004803603602081101561009857600080fd5b8101908080359060200190929190505050610283565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156100f15780820151818401526020810190506100d6565b505050509050019250505060405180910390f35b61010d6102ef565b6040518082815260200191505060405180910390f35b6101596004803603604081101561013957600080fd5b8101908080359060200190929190803590602001909291905050506102f9565b604051808215151515815260200191505060405180910390f35b61017b61031e565b604051808215151515815260200191505060405180910390f35b61019d610334565b005b61025f600480360360408110156101b557600080fd5b8101908080359060200190929190803590602001906401000000008111156101dc57600080fd5b8201836020820111156101ee57600080fd5b8035906020019184602083028401116401000000008311171561021057600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050610369565b604051808215151515815260200191505060405180910390f35b6102816103f3565b005b606061028e82610427565b61029757600080fd5b60028054806020026020016040519081016040528092919081815260200182805480156102e357602002820191906000526020600020905b8154815260200190600101908083116102cf575b50505050509050919050565b6000600154905090565b600061030483610427565b61030d57600080fd5b61031682610447565b905092915050565b60008060009054906101000a900460ff16905090565b6000809054906101000a900460ff161561034d57600080fd5b60016000806101000a81548160ff021916908315150217905550565b60008060009054906101000a900460ff161561038457600080fd5b6000600280549050141561039d5761039b8361052a565b505b6103a683610427565b6103af57600080fd5b60006103bb848461059c565b905060016000806101000a81548160ff0219169083151502179055506001600081548092919060010191905055508091505092915050565b6000809054906101000a900460ff1661040b57600080fd5b60008060006101000a81548160ff021916908315150217905550565b600080600360008481526020019081526020016000205414159050919050565b6000806003600084815260200190815260200160002054905060008111801561047557506002805490508111155b1561051f5760028054905081146104e357600060026001600280549050038154811061049d57fe5b9060005260206000200154905080600260018403815481106104bb57fe5b9060005260206000200181905550816003600083815260200190815260200160002081905550505b60016002818180549050039150816104fb919061087e565b50600060036000858152602001908152602001600020819055506001915050610525565b60009150505b919050565b600080600360008481526020019081526020016000205414156105925760028290806001815401808255809150509060018203906000526020600020016000909192909190915055600360008481526020019081526020016000208190555060019050610597565b600090505b919050565b6000806001905060008090505b8351811015610873578381815181106105be57fe5b6020026020010151851415610652577fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf21360008583815181106105fc57fe5b60200260200101516040518083151515158152602001828152602001806020018281038252602f8152602001806108f1602f9139604001935050505060405180910390a181801561064b575060005b9150610866565b61066e84828151811061066157fe5b6020026020010151610427565b15610715577fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf21360008583815181106106a257fe5b60200260200101516040518083151515158152602001828152602001806020018281038252601b8152602001807f4163636f756e7420697320616c72656164792061204d656d6265720000000000815250602001935050505060405180910390a181801561070e575060005b9150610865565b600061073385838151811061072657fe5b602002602001015161052a565b9050606081610777576040518060400160405280601b81526020017f4163636f756e7420697320616c72656164792061204d656d6265720000000000815250610791565b6040518060600160405280602181526020016108d0602191395b90507fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf213828785815181106107c157fe5b602002602001015183604051808415151515815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561081a5780820151818401526020810190506107ff565b50505050905090810190601f1680156108475780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a18380156108605750815b935050505b5b80806001019150506105a9565b508091505092915050565b8154818355818111156108a5578183600052602060002091820191016108a491906108aa565b5b505050565b6108cc91905b808211156108c85760008160009055506001016108b0565b5090565b9056fe4d656d626572206163636f756e74206164646564207375636365737366756c6c79416464696e67206f776e206163636f756e742061732061204d656d626572206973206e6f74207065726d6974746564a265627a7a7231582081adaba054a78ca50b49183102a909f50f15b49eb4947bfa3593139d8833895564736f6c63430005100032"); + + public static final Bytes ADD_TO_GROUP_METHOD_SIGNATURE = Bytes.fromHexString("0xf744b089"); + public static final Bytes CAN_EXECUTE_METHOD_SIGNATURE = Bytes.fromHexString("0x78b90337"); + public static final Bytes GET_PARTICIPANTS_METHOD_SIGNATURE = Bytes.fromHexString("0x0b0235be"); + public static final Bytes GET_VERSION_METHOD_SIGNATURE = Bytes.fromHexString("0x0d8e6e2c"); + public static final Bytes REMOVE_PARTICIPANT_METHOD_SIGNATURE = Bytes.fromHexString("0x61544c91"); + public static final Bytes LOCK_GROUP_METHOD_SIGNATURE = Bytes.fromHexString("0xf83d08ba"); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/FixedKeySigningPrivateMarkerTransactionFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/FixedKeySigningPrivateMarkerTransactionFactory.java index ad0283634..7f1ef993a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/FixedKeySigningPrivateMarkerTransactionFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/FixedKeySigningPrivateMarkerTransactionFactory.java @@ -40,8 +40,14 @@ public class FixedKeySigningPrivateMarkerTransactionFactory @Override public Transaction create( - final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Address precompileAddress) { return create( - transactionEnclaveKey, privateTransaction, nonceProvider.getNonce(sender), signingKey); + transactionEnclaveKey, + privateTransaction, + nonceProvider.getNonce(sender), + signingKey, + precompileAddress); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/PrivateMarkerTransactionFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/PrivateMarkerTransactionFactory.java index 2e6b4d646..a04113236 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/PrivateMarkerTransactionFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/PrivateMarkerTransactionFactory.java @@ -33,8 +33,15 @@ public abstract class PrivateMarkerTransactionFactory { return privacyPrecompileAddress; } + public Transaction create( + final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { + return create(transactionEnclaveKey, privateTransaction, privacyPrecompileAddress); + } + public abstract Transaction create( - final String transactionEnclaveKey, final PrivateTransaction privateTransaction); + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Address precompileAddress); protected Transaction create( final String transactionEnclaveKey, @@ -50,4 +57,20 @@ public abstract class PrivateMarkerTransactionFactory { .payload(Bytes.fromBase64String(transactionEnclaveKey)) .signAndBuild(signingKey); } + + protected Transaction create( + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final long nonce, + final KeyPair signingKey, + final Address precompileAddress) { + return Transaction.builder() + .nonce(nonce) + .gasPrice(privateTransaction.getGasPrice()) + .gasLimit(privateTransaction.getGasLimit()) + .to(precompileAddress) + .value(privateTransaction.getValue()) + .payload(Bytes.fromBase64String(transactionEnclaveKey)) + .signAndBuild(signingKey); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/RandomSigningPrivateMarkerTransactionFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/RandomSigningPrivateMarkerTransactionFactory.java index cad0f5573..b164920ba 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/RandomSigningPrivateMarkerTransactionFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/markertransaction/RandomSigningPrivateMarkerTransactionFactory.java @@ -27,8 +27,10 @@ public class RandomSigningPrivateMarkerTransactionFactory extends PrivateMarkerT @Override public Transaction create( - final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Address precompileAddress) { final KeyPair signingKey = KeyPair.generate(); - return create(transactionEnclaveKey, privateTransaction, 0, signingKey); + return create(transactionEnclaveKey, privateTransaction, 0, signingKey, precompileAddress); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java index c3ef13e72..38a18fa00 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java @@ -40,6 +40,7 @@ public class PrivateStateKeyValueStorage implements PrivateStateStorage { private static final Bytes PRIVACY_GROUP_HEAD_BLOCK_MAP_SUFFIX = Bytes.of("PGHEADMAP".getBytes(UTF_8)); private static final Bytes LEGACY_STATUS_KEY_SUFFIX = Bytes.of("STATUS".getBytes(UTF_8)); + private static final Bytes ADD_DATA_KEY = Bytes.of("ADDKEY".getBytes(UTF_8)); private final KeyValueStorage keyValueStorage; @@ -68,6 +69,11 @@ public class PrivateStateKeyValueStorage implements PrivateStateStorage { .map(b -> PrivacyGroupHeadBlockMap.readFrom(new BytesValueRLPInput(b, false))); } + @Override + public Optional getAddDataKey(final Bytes32 privacyGroupId) { + return get(privacyGroupId, ADD_DATA_KEY).map(Bytes32::wrap); + } + @Override public int getSchemaVersion() { return get(Bytes.EMPTY, DB_VERSION_KEY).map(Bytes::toInt).orElse(SCHEMA_VERSION_1_0_0); @@ -144,6 +150,13 @@ public class PrivateStateKeyValueStorage implements PrivateStateStorage { return this; } + @Override + public PrivateStateStorage.Updater putAddDataKey( + final Bytes32 privacyGroupId, final Bytes32 addDataKey) { + set(privacyGroupId, ADD_DATA_KEY, addDataKey); + return this; + } + @Override public void commit() { transaction.commit(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateStorage.java index 29d8b57db..c2d44422b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateStorage.java @@ -31,6 +31,8 @@ public interface PrivateStateStorage { int getSchemaVersion(); + Optional getAddDataKey(Bytes32 privacyGroupId); + boolean isEmpty(); Updater updater(); @@ -47,6 +49,8 @@ public interface PrivateStateStorage { Updater putDatabaseVersion(int version); + Updater putAddDataKey(Bytes32 privacyGroupId, Bytes32 addDataKey); + void commit(); void rollback(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateTransactionMetadata.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateTransactionMetadata.java index 01f95c2ea..a99ee5efe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateTransactionMetadata.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateTransactionMetadata.java @@ -18,6 +18,8 @@ import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPOutput; +import java.util.Objects; + /** Mined private transaction metadata. */ public class PrivateTransactionMetadata { private final Hash privacyMarkerTransactionHash; @@ -55,4 +57,18 @@ public class PrivateTransactionMetadata { input.leaveList(); return privateTransactionMetadata; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final PrivateTransactionMetadata that = (PrivateTransactionMetadata) o; + return privacyMarkerTransactionHash.equals(that.privacyMarkerTransactionHash) + && stateRoot.equals(that.stateRoot); + } + + @Override + public int hashCode() { + return Objects.hash(privacyMarkerTransactionHash, stateRoot); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index 8d09c9d6d..81d61f571 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; @@ -26,6 +27,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.Collections; @@ -39,12 +41,24 @@ public class PrivacyBlockProcessorTest { private PrivacyBlockProcessor privacyBlockProcessor; private PrivateStateStorage privateStateStorage; private AbstractBlockProcessor blockProcessor; + private WorldStateArchive privateWorldStateArchive; + private Enclave enclave; + private ProtocolSchedule protocolSchedule; @Before public void setUp() { blockProcessor = mock(AbstractBlockProcessor.class); privateStateStorage = new PrivateStateKeyValueStorage(new InMemoryKeyValueStorage()); - this.privacyBlockProcessor = new PrivacyBlockProcessor(blockProcessor, privateStateStorage); + privateWorldStateArchive = mock(WorldStateArchive.class); + enclave = mock(Enclave.class); + protocolSchedule = mock(ProtocolSchedule.class); + this.privacyBlockProcessor = + new PrivacyBlockProcessor( + blockProcessor, + protocolSchedule, + enclave, + privateStateStorage, + privateWorldStateArchive); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index 1acd06d37..7d1390e0d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -37,13 +37,14 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.privacy.storage.migration.PrivateTransactionDataFixture; +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.Base64; import java.util.List; import java.util.Optional; @@ -62,25 +63,10 @@ public class PrivacyPrecompiledContractTest { 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); - private static final byte[] VALID_PRIVATE_TRANSACTION_RLP_BASE64 = - Base64.getEncoder() - .encode( - Bytes.fromHexString( - "0xf90113800182520894095e7baea6a6c7c4c2dfeb977efac326af552d87" - + "a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - + "ffff801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d" - + "495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab94" - + "9f53faa07bd2c804ac41316156744d784c4355486d425648586f5a7a7a4267" - + "5062572f776a3561784470573958386c393153476f3df85aac41316156744d" - + "784c4355486d425648586f5a7a7a42675062572f776a356178447057395838" - + "6c393153476f3dac4b6f32625671442b6e4e6c4e594c35454537793349644f" - + "6e766966746a69697a706a52742b4854754642733d8a726573747269637465" - + "64") - .toArray()); - private PrivateTransactionProcessor mockPrivateTxProcessor() { final PrivateTransactionProcessor mockPrivateTransactionProcessor = mock(PrivateTransactionProcessor.class); @@ -146,10 +132,14 @@ public class PrivacyPrecompiledContractTest { new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage); contract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); + BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); + PrivateTransactionDataFixture.privateTransaction(PAYLOAD_TEST_PRIVACY_GROUP_ID) + .writeTo(bytesValueRLPOutput); + final ReceiveResponse response = new ReceiveResponse( - VALID_PRIVATE_TRANSACTION_RLP_BASE64, - "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o=", + bytesValueRLPOutput.encoded().toBase64String().getBytes(UTF_8), + PAYLOAD_TEST_PRIVACY_GROUP_ID, null); when(enclave.receive(any(String.class))).thenReturn(response); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java index 78dafc7e1..7702cc2a3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyControllerTest.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.privacy; import static com.google.common.collect.Lists.newArrayList; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE; @@ -37,20 +38,25 @@ import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.enclave.types.PrivacyGroup.Type; import org.hyperledger.besu.enclave.types.ReceiveResponse; import org.hyperledger.besu.enclave.types.SendResponse; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Log; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.privacy.markertransaction.FixedKeySigningPrivateMarkerTransactionFactory; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.orion.testutil.OrionKeyUtils; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; @@ -78,6 +84,8 @@ public class DefaultPrivacyControllerTest { private static final String ENCLAVE_KEY2 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; private static final String PRIVACY_GROUP_ID = "DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="; private static final ArrayList LOGS = new ArrayList<>(); + private static final String MOCK_TRANSACTION_SIMULATOR_RESULT_OUTPUT_BYTES_PREFIX = + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002"; private PrivacyController privacyController; private PrivacyController brokenPrivacyController; @@ -86,6 +94,9 @@ public class DefaultPrivacyControllerTest { private String enclavePublicKey; private PrivateNonceProvider privateNonceProvider; private PrivateWorldStateReader privateWorldStateReader; + private PrivateTransactionSimulator privateTransactionSimulator; + private Blockchain blockchain; + private PrivateStateStorage privateStateStorage; private static final Transaction PUBLIC_TRANSACTION = Transaction.builder() @@ -123,18 +134,11 @@ public class DefaultPrivacyControllerTest { return validator; } - private PrivateTransactionSimulator mockPrivateTransactionSimulator() { - final PrivateTransactionSimulator simulator = mock(PrivateTransactionSimulator.class); - when(simulator.process(any(), any(), any(long.class))) - .thenReturn( - Optional.of( - PrivateTransactionProcessor.Result.successful( - LOGS, 0, Bytes.EMPTY, ValidationResult.valid()))); - return simulator; - } - @Before public void setUp() throws Exception { + blockchain = mock(Blockchain.class); + privateTransactionSimulator = mock(PrivateTransactionSimulator.class); + privateStateStorage = mock(PrivateStateStorage.class); privateNonceProvider = mock(ChainHeadPrivateNonceProvider.class); when(privateNonceProvider.getNonce(any(), any())).thenReturn(1L); @@ -143,11 +147,11 @@ public class DefaultPrivacyControllerTest { enclavePublicKey = OrionKeyUtils.loadKey("orion_key_0.pub"); privateTransactionValidator = mockPrivateTransactionValidator(); enclave = mockEnclave(); - final PrivateTransactionSimulator privateTransactionSimulator = - mockPrivateTransactionSimulator(); privacyController = new DefaultPrivacyController( + blockchain, + privateStateStorage, enclave, privateTransactionValidator, new FixedKeySigningPrivateMarkerTransactionFactory( @@ -157,6 +161,8 @@ public class DefaultPrivacyControllerTest { privateWorldStateReader); brokenPrivacyController = new DefaultPrivacyController( + blockchain, + privateStateStorage, brokenMockEnclave(), privateTransactionValidator, new FixedKeySigningPrivateMarkerTransactionFactory( @@ -192,6 +198,9 @@ public class DefaultPrivacyControllerTest { public void sendValidBesuTransaction() { final PrivateTransaction transaction = buildBesuPrivateTransaction(1); + when(enclave.retrievePrivacyGroup(any(String.class))) + .thenReturn(new PrivacyGroup("", Type.PANTHEON, "", "", emptyList())); + final String enclaveKey = privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); final ValidationResult validationResult = @@ -209,6 +218,41 @@ public class DefaultPrivacyControllerTest { verify(enclave).send(anyString(), eq(ENCLAVE_PUBLIC_KEY), eq(PRIVACY_GROUP_ID)); } + @Test + public void findOnChainPrivacyGroups() { + final List privacyGroupAddresses = newArrayList(ENCLAVE_PUBLIC_KEY, ENCLAVE_KEY2); + + final PrivacyGroup privacyGroup = + new PrivacyGroup(PRIVACY_GROUP_ID, Type.ONCHAIN, "", "", privacyGroupAddresses); + + final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = + new PrivacyGroupHeadBlockMap( + Map.of(Bytes32.wrap(Bytes.fromBase64String(PRIVACY_GROUP_ID)), Hash.ZERO)); + when(privateStateStorage.getPrivacyGroupHeadBlockMap(any())) + .thenReturn(Optional.of(privacyGroupHeadBlockMap)); + + when(privateTransactionSimulator.process(any(), any())) + .thenReturn( + Optional.of( + new PrivateTransactionProcessor.Result( + TransactionProcessor.Result.Status.SUCCESSFUL, + emptyList(), + 0, + Bytes.fromHexString( + MOCK_TRANSACTION_SIMULATOR_RESULT_OUTPUT_BYTES_PREFIX + + Bytes.fromBase64String(ENCLAVE_PUBLIC_KEY).toUnprefixedHexString() + + Bytes.fromBase64String(ENCLAVE_KEY2).toUnprefixedHexString()), + ValidationResult.valid(), + Optional.empty()))); + + final List privacyGroups = + privacyController.findOnChainPrivacyGroup(privacyGroupAddresses, ENCLAVE_PUBLIC_KEY); + assertThat(privacyGroups).hasSize(1); + assertThat(privacyGroups.get(0)).isEqualToComparingFieldByField(privacyGroup); + verify(privateStateStorage).getPrivacyGroupHeadBlockMap(any()); + verify(privateTransactionSimulator).process(any(), any()); + } + @Test public void sendTransactionWhenEnclaveFailsThrowsEnclaveError() { assertThatExceptionOfType(EnclaveServerException.class) @@ -373,12 +417,40 @@ public class DefaultPrivacyControllerTest { @Test public void simulatingPrivateTransactionWorks() { final CallParameter callParameter = mock(CallParameter.class); + when(privateTransactionSimulator.process(any(), any(), any(long.class))) + .thenReturn( + Optional.of( + PrivateTransactionProcessor.Result.successful( + LOGS, 0, Bytes.EMPTY, ValidationResult.valid()))); final Optional result = privacyController.simulatePrivateTransaction( "Group1", ENCLAVE_PUBLIC_KEY, callParameter, 1); assertThat(result.isPresent()).isTrue(); } + @Test + public void canCreatePrivacyMarkerTransactionForOnChainPrivacy() { + final PrivateTransaction transaction = buildBesuPrivateTransaction(0); + + final String enclaveKey = privacyController.sendTransaction(transaction, ENCLAVE_PUBLIC_KEY); + + final Transaction onChainPrivacyMarkerTransaction = + privacyController.createPrivacyMarkerTransaction( + enclaveKey, transaction, Address.ONCHAIN_PRIVACY); + + assertThat(onChainPrivacyMarkerTransaction.contractAddress()) + .isEqualTo(PUBLIC_TRANSACTION.contractAddress()); + assertThat(onChainPrivacyMarkerTransaction.getPayload()) + .isEqualTo(PUBLIC_TRANSACTION.getPayload()); + assertThat(onChainPrivacyMarkerTransaction.getNonce()).isEqualTo(PUBLIC_TRANSACTION.getNonce()); + assertThat(onChainPrivacyMarkerTransaction.getSender()) + .isEqualTo(PUBLIC_TRANSACTION.getSender()); + assertThat(onChainPrivacyMarkerTransaction.getValue()).isEqualTo(PUBLIC_TRANSACTION.getValue()); + assertThat(onChainPrivacyMarkerTransaction.getTo().get()).isEqualTo(Address.ONCHAIN_PRIVACY); + verify(enclave) + .send(anyString(), eq(ENCLAVE_PUBLIC_KEY), eq(singletonList(ENCLAVE_PUBLIC_KEY))); + } + @Test public void getContractCodeCallsPrivateWorldStateReader() { final Hash blockHash = Hash.ZERO; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java index 1be5aa6f8..026b488c3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyControllerTest.java @@ -232,7 +232,12 @@ public class MultiTenancyPrivacyControllerTest { public void findsPrivacyGroupWhenEnclavePublicKeyInAddresses() { final List addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2); final PrivacyGroup privacyGroup = - new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2)); + new PrivacyGroup( + PRIVACY_GROUP_ID, + Type.PANTHEON, + "", + "", + List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2)); when(privacyController.findPrivacyGroup(addresses, ENCLAVE_PUBLIC_KEY1)) .thenReturn(new PrivacyGroup[] {privacyGroup}); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateTransactionDataFixture.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateTransactionDataFixture.java index cb15b0ff7..c4ecb1e0f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateTransactionDataFixture.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateTransactionDataFixture.java @@ -38,7 +38,7 @@ public class PrivateTransactionDataFixture { private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); - static Transaction privacyMarkerTransaction(final String transactionKey) { + public static Transaction privacyMarkerTransaction(final String transactionKey) { return Transaction.builder() .nonce(0) .gasPrice(Wei.of(1000)) @@ -51,7 +51,7 @@ public class PrivateTransactionDataFixture { .signAndBuild(KEY_PAIR); } - static PrivateTransaction privateTransaction(final String privacyGroupId) { + public static PrivateTransaction privateTransaction(final String privacyGroupId) { return PrivateTransaction.builder() .nonce(0) .gasPrice(Wei.of(1000)) diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java index fa666c873..92650c89c 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockImporter; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.math.BigInteger; import java.util.Optional; @@ -84,4 +85,10 @@ public class NoRewardProtocolScheduleWrapper implements ProtocolSchedule { public void setTransactionFilter(final TransactionFilter transactionFilter) { delegate.setTransactionFilter(transactionFilter); } + + @Override + public void setPublicWorldStateArchiveForPrivacyBlockProcessor( + final WorldStateArchive publicWorldStateArchive) { + delegate.setPublicWorldStateArchiveForPrivacyBlockProcessor(publicWorldStateArchive); + } } diff --git a/privacy-contracts/build.gradle b/privacy-contracts/build.gradle new file mode 100644 index 000000000..2c83727a8 --- /dev/null +++ b/privacy-contracts/build.gradle @@ -0,0 +1,24 @@ +/* + * 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 + */ + +jar { enabled = true } +dependencies { + implementation 'net.consensys:orion' + implementation 'org.apache.tuweni:tuweni-bytes' + implementation 'org.apache.tuweni:tuweni-io' + implementation 'org.apache.tuweni:tuweni-toml' + implementation 'org.web3j:abi' + implementation 'org.web3j:besu' +} diff --git a/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyGroup.java b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyGroup.java new file mode 100644 index 000000000..0fa7ffd70 --- /dev/null +++ b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyGroup.java @@ -0,0 +1,316 @@ +/* + * 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.privacy.contracts.generated; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; +import org.web3j.abi.EventEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Int256; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + * Auto generated code. + * + *

Do not modify! + * + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the codegen module to update. + * + *

Generated with web3j versionimport static org.junit.Assert.assertThat; 4.5.14. + */ +@SuppressWarnings("rawtypes") +public class PrivacyGroup extends Contract { + public static final String BINARY = + "608060405234801561001057600080fd5b50610954806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c806378b903371161005b57806378b9033714610173578063a69df4b514610195578063f744b0891461019f578063f83d08ba146102795761007d565b80630b0235be146100825780630d8e6e2c1461010557806361544c9114610123575b600080fd5b6100ae6004803603602081101561009857600080fd5b8101908080359060200190929190505050610283565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156100f15780820151818401526020810190506100d6565b505050509050019250505060405180910390f35b61010d6102ef565b6040518082815260200191505060405180910390f35b6101596004803603604081101561013957600080fd5b8101908080359060200190929190803590602001909291905050506102f9565b604051808215151515815260200191505060405180910390f35b61017b61031e565b604051808215151515815260200191505060405180910390f35b61019d610334565b005b61025f600480360360408110156101b557600080fd5b8101908080359060200190929190803590602001906401000000008111156101dc57600080fd5b8201836020820111156101ee57600080fd5b8035906020019184602083028401116401000000008311171561021057600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050610369565b604051808215151515815260200191505060405180910390f35b6102816103f3565b005b606061028e82610427565b61029757600080fd5b60028054806020026020016040519081016040528092919081815260200182805480156102e357602002820191906000526020600020905b8154815260200190600101908083116102cf575b50505050509050919050565b6000600154905090565b600061030483610427565b61030d57600080fd5b61031682610447565b905092915050565b60008060009054906101000a900460ff16905090565b6000809054906101000a900460ff161561034d57600080fd5b60016000806101000a81548160ff021916908315150217905550565b60008060009054906101000a900460ff161561038457600080fd5b6000600280549050141561039d5761039b8361052a565b505b6103a683610427565b6103af57600080fd5b60006103bb848461059c565b905060016000806101000a81548160ff0219169083151502179055506001600081548092919060010191905055508091505092915050565b6000809054906101000a900460ff1661040b57600080fd5b60008060006101000a81548160ff021916908315150217905550565b600080600360008481526020019081526020016000205414159050919050565b6000806003600084815260200190815260200160002054905060008111801561047557506002805490508111155b1561051f5760028054905081146104e357600060026001600280549050038154811061049d57fe5b9060005260206000200154905080600260018403815481106104bb57fe5b9060005260206000200181905550816003600083815260200190815260200160002081905550505b60016002818180549050039150816104fb919061087e565b50600060036000858152602001908152602001600020819055506001915050610525565b60009150505b919050565b600080600360008481526020019081526020016000205414156105925760028290806001815401808255809150509060018203906000526020600020016000909192909190915055600360008481526020019081526020016000208190555060019050610597565b600090505b919050565b6000806001905060008090505b8351811015610873578381815181106105be57fe5b6020026020010151851415610652577fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf21360008583815181106105fc57fe5b60200260200101516040518083151515158152602001828152602001806020018281038252602f8152602001806108f1602f9139604001935050505060405180910390a181801561064b575060005b9150610866565b61066e84828151811061066157fe5b6020026020010151610427565b15610715577fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf21360008583815181106106a257fe5b60200260200101516040518083151515158152602001828152602001806020018281038252601b8152602001807f4163636f756e7420697320616c72656164792061204d656d6265720000000000815250602001935050505060405180910390a181801561070e575060005b9150610865565b600061073385838151811061072657fe5b602002602001015161052a565b9050606081610777576040518060400160405280601b81526020017f4163636f756e7420697320616c72656164792061204d656d6265720000000000815250610791565b6040518060600160405280602181526020016108d0602191395b90507fcc7365305ae5f16c463d1383713d699f43c5548bbda5537ee61373ceb9aaf213828785815181106107c157fe5b602002602001015183604051808415151515815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561081a5780820151818401526020810190506107ff565b50505050905090810190601f1680156108475780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a18380156108605750815b935050505b5b80806001019150506105a9565b508091505092915050565b8154818355818111156108a5578183600052602060002091820191016108a491906108aa565b5b505050565b6108cc91905b808211156108c85760008160009055506001016108b0565b5090565b9056fe4d656d626572206163636f756e74206164646564207375636365737366756c6c79416464696e67206f776e206163636f756e742061732061204d656d626572206973206e6f74207065726d6974746564a265627a7a723158209b8cbc4503135a6654e90319f2db6e4559f25bd3ae7c55c69c7c7c52670c67fa64736f6c634300050c0032"; + + public static final String FUNC_ADDPARTICIPANTS = "addParticipants"; + + public static final String FUNC_CANEXECUTE = "canExecute"; + + public static final String FUNC_GETPARTICIPANTS = "getParticipants"; + + public static final String FUNC_GETVERSION = "getVersion"; + + public static final String FUNC_LOCK = "lock"; + + public static final String FUNC_REMOVEPARTICIPANT = "removeParticipant"; + + public static final String FUNC_UNLOCK = "unlock"; + + public static final Event PARTICIPANTADDED_EVENT = + new Event( + "ParticipantAdded", + Arrays.>asList( + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}));; + + @Deprecated + protected PrivacyGroup( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected PrivacyGroup( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected PrivacyGroup( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected PrivacyGroup( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public List getParticipantAddedEvents( + TransactionReceipt transactionReceipt) { + List valueList = + extractEventParametersWithLog(PARTICIPANTADDED_EVENT, transactionReceipt); + ArrayList responses = + new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + ParticipantAddedEventResponse typedResponse = new ParticipantAddedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.adminAdded = (Boolean) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.account = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.message = (String) eventValues.getNonIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public Flowable participantAddedEventFlowable(EthFilter filter) { + return web3j + .ethLogFlowable(filter) + .map( + new Function() { + @Override + public ParticipantAddedEventResponse apply(Log log) { + Contract.EventValuesWithLog eventValues = + extractEventParametersWithLog(PARTICIPANTADDED_EVENT, log); + ParticipantAddedEventResponse typedResponse = new ParticipantAddedEventResponse(); + typedResponse.log = log; + typedResponse.adminAdded = + (Boolean) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.account = + (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.message = + (String) eventValues.getNonIndexedValues().get(2).getValue(); + return typedResponse; + } + }); + } + + public Flowable participantAddedEventFlowable( + DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PARTICIPANTADDED_EVENT)); + return participantAddedEventFlowable(filter); + } + + public RemoteFunctionCall addParticipants( + byte[] _enclaveKey, List _accounts) { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_ADDPARTICIPANTS, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey), + new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.generated.Bytes32.class, + org.web3j.abi.Utils.typeMap( + _accounts, org.web3j.abi.datatypes.generated.Bytes32.class))), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall canExecute() { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_CANEXECUTE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall getParticipants(byte[] _enclaveKey) { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_GETPARTICIPANTS, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey)), + Arrays.>asList(new TypeReference>() {})); + return new RemoteFunctionCall( + function, + new Callable() { + @Override + @SuppressWarnings("unchecked") + public List call() throws Exception { + List result = (List) executeCallSingleValueReturn(function, List.class); + return convertToNative(result); + } + }); + } + + public RemoteFunctionCall getVersion() { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_GETVERSION, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall lock() { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_LOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall removeParticipant( + byte[] _enclaveKey, byte[] _account) { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_REMOVEPARTICIPANT, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey), + new org.web3j.abi.datatypes.generated.Bytes32(_account)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall unlock() { + final org.web3j.abi.datatypes.Function function = + new org.web3j.abi.datatypes.Function( + FUNC_UNLOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + @Deprecated + public static PrivacyGroup load( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyGroup(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static PrivacyGroup load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyGroup(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static PrivacyGroup load( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + return new PrivacyGroup(contractAddress, web3j, credentials, contractGasProvider); + } + + public static PrivacyGroup load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + return new PrivacyGroup(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + PrivacyGroup.class, web3j, credentials, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return deployRemoteCall(PrivacyGroup.class, web3j, credentials, gasPrice, gasLimit, BINARY, ""); + } + + public static RemoteCall deploy( + Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + PrivacyGroup.class, web3j, transactionManager, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return deployRemoteCall( + PrivacyGroup.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, ""); + } + + public static class ParticipantAddedEventResponse extends BaseEventResponse { + public Boolean adminAdded; + + public byte[] account; + + public String message; + } +} diff --git a/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyInterface.java b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyInterface.java new file mode 100644 index 000000000..3563b0c7f --- /dev/null +++ b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyInterface.java @@ -0,0 +1,243 @@ +/* + * 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.privacy.contracts.generated; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Int256; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + * Auto generated code. + * + *

Do not modify! + * + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the codegen module to update. + * + *

Generated with web3j version 4.5.14. + */ +@SuppressWarnings("rawtypes") +public class PrivacyInterface extends Contract { + public static final String BINARY = ""; + + public static final String FUNC_ADDPARTICIPANTS = "addParticipants"; + + public static final String FUNC_CANEXECUTE = "canExecute"; + + public static final String FUNC_GETPARTICIPANTS = "getParticipants"; + + public static final String FUNC_GETVERSION = "getVersion"; + + public static final String FUNC_LOCK = "lock"; + + public static final String FUNC_REMOVEPARTICIPANT = "removeParticipant"; + + public static final String FUNC_UNLOCK = "unlock"; + + @Deprecated + protected PrivacyInterface( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected PrivacyInterface( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected PrivacyInterface( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected PrivacyInterface( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteFunctionCall addParticipants( + byte[] enclaveKey, List participants) { + final Function function = + new Function( + FUNC_ADDPARTICIPANTS, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey), + new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.generated.Bytes32.class, + org.web3j.abi.Utils.typeMap( + participants, org.web3j.abi.datatypes.generated.Bytes32.class))), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall canExecute() { + final Function function = + new Function( + FUNC_CANEXECUTE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall getParticipants(byte[] enclaveKey) { + final Function function = + new Function( + FUNC_GETPARTICIPANTS, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey)), + Arrays.>asList(new TypeReference>() {})); + return new RemoteFunctionCall( + function, + new Callable() { + @Override + @SuppressWarnings("unchecked") + public List call() throws Exception { + List result = (List) executeCallSingleValueReturn(function, List.class); + return convertToNative(result); + } + }); + } + + public RemoteFunctionCall getVersion() { + final Function function = + new Function( + FUNC_GETVERSION, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall lock() { + final Function function = + new Function(FUNC_LOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall removeParticipant( + byte[] enclaveKey, byte[] account) { + final Function function = + new Function( + FUNC_REMOVEPARTICIPANT, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey), + new org.web3j.abi.datatypes.generated.Bytes32(account)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall unlock() { + final Function function = + new Function(FUNC_UNLOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + @Deprecated + public static PrivacyInterface load( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyInterface(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static PrivacyInterface load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyInterface(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static PrivacyInterface load( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + return new PrivacyInterface(contractAddress, web3j, credentials, contractGasProvider); + } + + public static PrivacyInterface load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + return new PrivacyInterface(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + PrivacyInterface.class, web3j, credentials, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return deployRemoteCall( + PrivacyInterface.class, web3j, credentials, gasPrice, gasLimit, BINARY, ""); + } + + public static RemoteCall deploy( + Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return deployRemoteCall( + PrivacyInterface.class, web3j, transactionManager, contractGasProvider, BINARY, ""); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return deployRemoteCall( + PrivacyInterface.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, ""); + } +} diff --git a/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyProxy.java b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyProxy.java new file mode 100644 index 000000000..4726fdb89 --- /dev/null +++ b/privacy-contracts/src/main/java/org/hyperledger/besu/privacy/contracts/generated/PrivacyProxy.java @@ -0,0 +1,302 @@ +/* + * 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.privacy.contracts.generated; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Int256; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + * Auto generated code. + * + *

Do not modify! + * + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the codegen module to update. + * + *

Generated with web3j version 4.5.14. + */ +@SuppressWarnings("rawtypes") +public class PrivacyProxy extends Contract { + public static final String BINARY = + "608060405234801561001057600080fd5b50604051610a4f380380610a4f8339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506109bb806100946000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c806361544c911161006657806361544c91146101c757806378b9033714610217578063a69df4b514610239578063f744b08914610243578063f83d08ba1461031d57610093565b80630b0235be146100985780630d8e6e2c1461011b5780633659cfe6146101395780635c60da1b1461017d575b600080fd5b6100c4600480360360208110156100ae57600080fd5b8101908080359060200190929190505050610327565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156101075780820151818401526020810190506100ec565b505050509050019250505060405180910390f35b61012361047d565b6040518082815260200191505060405180910390f35b61017b6004803603602081101561014f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061052b565b005b610185610591565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101fd600480360360408110156101dd57600080fd5b8101908080359060200190929190803590602001909291905050506105b6565b604051808215151515815260200191505060405180910390f35b61021f61067c565b604051808215151515815260200191505060405180910390f35b61024161072a565b005b6103036004803603604081101561025957600080fd5b81019080803590602001909291908035906020019064010000000081111561028057600080fd5b82018360208201111561029257600080fd5b803590602001918460208302840111640100000000831117156102b457600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506107b3565b604051808215151515815260200191505060405180910390f35b6103256108ba565b005b606060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff16630b0235be846040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b1580156103a057600080fd5b505afa1580156103b4573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525060208110156103de57600080fd5b81019080805160405193929190846401000000008211156103fe57600080fd5b8382019150602082018581111561041457600080fd5b825186602082028301116401000000008211171561043157600080fd5b8083526020830192505050908051906020019060200280838360005b8381101561046857808201518184015260208101905061044d565b50505050905001604052505050915050919050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff16630d8e6e2c6040518163ffffffff1660e01b815260040160206040518083038186803b1580156104ea57600080fd5b505afa1580156104fe573d6000803e3d6000fd5b505050506040513d602081101561051457600080fd5b810190808051906020019092919050505091505090565b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561058557600080fd5b61058e81610943565b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff166361544c9185856040518363ffffffff1660e01b81526004018083815260200182815260200192505050602060405180830381600087803b15801561063857600080fd5b505af115801561064c573d6000803e3d6000fd5b505050506040513d602081101561066257600080fd5b810190808051906020019092919050505091505092915050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff166378b903376040518163ffffffff1660e01b815260040160206040518083038186803b1580156106e957600080fd5b505afa1580156106fd573d6000803e3d6000fd5b505050506040513d602081101561071357600080fd5b810190808051906020019092919050505091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663a69df4b56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561079857600080fd5b505af11580156107ac573d6000803e3d6000fd5b5050505050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663f744b08985856040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b83811015610850578082015181840152602081019050610835565b505050509050019350505050602060405180830381600087803b15801561087657600080fd5b505af115801561088a573d6000803e3d6000fd5b505050506040513d60208110156108a057600080fd5b810190808051906020019092919050505091505092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff1663f83d08ba6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561092857600080fd5b505af115801561093c573d6000803e3d6000fd5b5050505050565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820e33ecd4eea8b7a726b69d0db7cbc6b26d018a34ebd1b88911b5506edc542777264736f6c634300050c0032"; + + public static final String FUNC_ADDPARTICIPANTS = "addParticipants"; + + public static final String FUNC_CANEXECUTE = "canExecute"; + + public static final String FUNC_GETPARTICIPANTS = "getParticipants"; + + public static final String FUNC_GETVERSION = "getVersion"; + + public static final String FUNC_IMPLEMENTATION = "implementation"; + + public static final String FUNC_LOCK = "lock"; + + public static final String FUNC_REMOVEPARTICIPANT = "removeParticipant"; + + public static final String FUNC_UNLOCK = "unlock"; + + public static final String FUNC_UPGRADETO = "upgradeTo"; + + @Deprecated + protected PrivacyProxy( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected PrivacyProxy( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected PrivacyProxy( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected PrivacyProxy( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteFunctionCall addParticipants( + byte[] enclaveKey, List participants) { + final Function function = + new Function( + FUNC_ADDPARTICIPANTS, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey), + new org.web3j.abi.datatypes.DynamicArray( + org.web3j.abi.datatypes.generated.Bytes32.class, + org.web3j.abi.Utils.typeMap( + participants, org.web3j.abi.datatypes.generated.Bytes32.class))), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall canExecute() { + final Function function = + new Function( + FUNC_CANEXECUTE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall getParticipants(byte[] enclaveKey) { + final Function function = + new Function( + FUNC_GETPARTICIPANTS, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey)), + Arrays.>asList(new TypeReference>() {})); + return new RemoteFunctionCall( + function, + new Callable() { + @Override + @SuppressWarnings("unchecked") + public List call() throws Exception { + List result = (List) executeCallSingleValueReturn(function, List.class); + return convertToNative(result); + } + }); + } + + public RemoteFunctionCall getVersion() { + final Function function = + new Function( + FUNC_GETVERSION, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall implementation() { + final Function function = + new Function( + FUNC_IMPLEMENTATION, + Arrays.asList(), + Arrays.>asList(new TypeReference

() {})); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall lock() { + final Function function = + new Function(FUNC_LOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall removeParticipant( + byte[] enclaveKey, byte[] account) { + final Function function = + new Function( + FUNC_REMOVEPARTICIPANT, + Arrays.asList( + new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey), + new org.web3j.abi.datatypes.generated.Bytes32(account)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall unlock() { + final Function function = + new Function(FUNC_UNLOCK, Arrays.asList(), Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall upgradeTo(String _newImplementation) { + final Function function = + new Function( + FUNC_UPGRADETO, + Arrays.asList(new org.web3j.abi.datatypes.Address(160, _newImplementation)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + @Deprecated + public static PrivacyProxy load( + String contractAddress, + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyProxy(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static PrivacyProxy load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit) { + return new PrivacyProxy(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static PrivacyProxy load( + String contractAddress, + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider) { + return new PrivacyProxy(contractAddress, web3j, credentials, contractGasProvider); + } + + public static PrivacyProxy load( + String contractAddress, + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider) { + return new PrivacyProxy(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy( + Web3j web3j, + Credentials credentials, + ContractGasProvider contractGasProvider, + String _implementation) { + String encodedConstructor = + FunctionEncoder.encodeConstructor( + Arrays.asList(new org.web3j.abi.datatypes.Address(160, _implementation))); + return deployRemoteCall( + PrivacyProxy.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); + } + + public static RemoteCall deploy( + Web3j web3j, + TransactionManager transactionManager, + ContractGasProvider contractGasProvider, + String _implementation) { + String encodedConstructor = + FunctionEncoder.encodeConstructor( + Arrays.asList(new org.web3j.abi.datatypes.Address(160, _implementation))); + return deployRemoteCall( + PrivacyProxy.class, + web3j, + transactionManager, + contractGasProvider, + BINARY, + encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, + Credentials credentials, + BigInteger gasPrice, + BigInteger gasLimit, + String _implementation) { + String encodedConstructor = + FunctionEncoder.encodeConstructor( + Arrays.asList(new org.web3j.abi.datatypes.Address(160, _implementation))); + return deployRemoteCall( + PrivacyProxy.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy( + Web3j web3j, + TransactionManager transactionManager, + BigInteger gasPrice, + BigInteger gasLimit, + String _implementation) { + String encodedConstructor = + FunctionEncoder.encodeConstructor( + Arrays.asList(new org.web3j.abi.datatypes.Address(160, _implementation))); + return deployRemoteCall( + PrivacyProxy.class, + web3j, + transactionManager, + gasPrice, + gasLimit, + BINARY, + encodedConstructor); + } +} diff --git a/privacy-contracts/src/main/solidity/DefaultOnChainPrivacyGroupManagementContract.sol b/privacy-contracts/src/main/solidity/DefaultOnChainPrivacyGroupManagementContract.sol new file mode 100644 index 000000000..03264778e --- /dev/null +++ b/privacy-contracts/src/main/solidity/DefaultOnChainPrivacyGroupManagementContract.sol @@ -0,0 +1,106 @@ +pragma solidity ^0.5.9; +import "./OnChainPrivacyGroupManagementInterface.sol"; + +contract DefaultOnChainPrivacyGroupManagementContract is OnChainPrivacyGroupManagementInterface { + + bool private _canExecute; + int private _version; + bytes32[] private distributionList; + mapping(bytes32 => uint256) private distributionIndexOf; + + function getVersion() external view returns (int) { + return _version; + } + + // overrides + function canExecute() external view returns (bool) { + return _canExecute; + } + + function lock() public { + require(_canExecute); + _canExecute = false; + } + + function unlock() public { + require(!_canExecute); + _canExecute = true; + } + + function addParticipants(bytes32 _enclaveKey, bytes32[] memory _accounts) public returns (bool) { + require(!_canExecute); + if(distributionList.length == 0) { + addParticipant(_enclaveKey); + } + require(isMember(_enclaveKey)); + bool result = addAll(_enclaveKey, _accounts); + _canExecute = true; + _version++; + return result; + } + + function removeParticipant(bytes32 _enclaveKey, bytes32 _account) public returns (bool) { + require(isMember(_enclaveKey)); + return removeInternal(_account); + } + + function getParticipants(bytes32 _enclaveKey) public view returns (bytes32[] memory) { + require(isMember(_enclaveKey)); + return distributionList; + } + + + //internal functions + function addAll(bytes32 _enclaveKey, bytes32[] memory _accounts) 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"); + allAdded = allAdded && false; + } else { + bool result = addParticipant(_accounts[i]); + string memory message = result ? "Member account added successfully" : "Account is already a Member"; + emit ParticipantAdded(result, _accounts[i], message); + allAdded = allAdded && result; + } + } + return allAdded; + } + + function isMember(bytes32 _account) internal view returns (bool) { + return distributionIndexOf[_account] != 0; + } + + function addParticipant(bytes32 _participant) internal returns (bool) { + if (distributionIndexOf[_participant] == 0) { + distributionIndexOf[_participant] = distributionList.push(_participant); + return true; + } + return false; + } + + function removeInternal(bytes32 _participant) internal returns (bool) { + uint256 index = distributionIndexOf[_participant]; + 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; + } + distributionList.length -= 1; + distributionIndexOf[_participant] = 0; + return true; + } + return false; + } + + event ParticipantAdded( + bool adminAdded, + bytes32 account, + string message + ); +} \ No newline at end of file diff --git a/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementInterface.sol b/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementInterface.sol new file mode 100644 index 000000000..8c1b66f98 --- /dev/null +++ b/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementInterface.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.5.9; + +interface OnChainPrivacyGroupManagementInterface { + + function addParticipants(bytes32 enclaveKey, bytes32[] calldata participants) external returns (bool); + + function removeParticipant(bytes32 enclaveKey, bytes32 account) external returns (bool); + + function getParticipants(bytes32 enclaveKey) external view returns (bytes32[] memory); + + function lock() external; + + function unlock() external; + + function canExecute() external view returns (bool); + + function getVersion() external view returns (int); +} diff --git a/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementProxy.sol b/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementProxy.sol new file mode 100644 index 000000000..f2feb613b --- /dev/null +++ b/privacy-contracts/src/main/solidity/OnChainPrivacyGroupManagementProxy.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.5.12; + +import "./OnChainPrivacyGroupManagementInterface.sol"; + +contract OnChainPrivacyGroupManagementProxy is OnChainPrivacyGroupManagementInterface { + + address public implementation; + + constructor(address _implementation) public { + 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) { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.addParticipants(enclaveKey, participants); + } + + function getParticipants(bytes32 enclaveKey) view public returns (bytes32[] memory) { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.getParticipants(enclaveKey); + } + + function removeParticipant(bytes32 enclaveKey, bytes32 account) public returns (bool) { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.removeParticipant(enclaveKey, account); + } + + function lock() public { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.lock(); + } + + function unlock() public { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.unlock(); + } + + function canExecute() public view returns (bool) { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.canExecute(); + } + + function getVersion() public view returns (int) { + OnChainPrivacyGroupManagementInterface privacyInterface = OnChainPrivacyGroupManagementInterface(implementation); + return privacyInterface.getVersion(); + } +} diff --git a/privacy-contracts/src/main/solidity/generateWrappers.sh b/privacy-contracts/src/main/solidity/generateWrappers.sh new file mode 100755 index 000000000..efd32d46f --- /dev/null +++ b/privacy-contracts/src/main/solidity/generateWrappers.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +targets=" +OnChainPrivacyGroupManagementInterface +DefaultOnChainPrivacyGroupManagementContract +OnChainPrivacyGroupManagementProxy +" + +for target in ${targets}; do + + solc --overwrite --bin --abi \ + -o build \ + ${target}.sol + +done + +for target in ${targets}; do + + web3j solidity generate \ + -b build/${target}.bin \ + -a build/${target}.abi \ + -o ../java \ + -p org.hyperledger.besu.privacy.contracts.generated + +done \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 4571f8c2f..17a156687 100644 --- a/settings.gradle +++ b/settings.gradle @@ -47,3 +47,4 @@ include 'services:pipeline' include 'services:tasks' include 'testutil' include 'util' +include 'privacy-contracts'