on-chain-privacy-groups (#423)

Introduce on-chain privacy groups with add and remove functionality.

Signed-off-by: Ivaylo Kirilov <iikirilov@gmail.com>
This commit is contained in:
Ivaylo Kirilov
2020-03-05 21:39:09 +00:00
committed by GitHub
parent 2a3d64e82e
commit 9701a92dc9
88 changed files with 5106 additions and 248 deletions

View File

@@ -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<String> transactionHashes) {
return new NewPendingTransactionFilterChangesCondition(
transactions.filterChanges(filterId), transactionHashes);
}
}

View File

@@ -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<String> transactionHashes;
public NewPendingTransactionFilterChangesCondition(
final EthFilterChangesTransaction filterChanges, final List<String> 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));
}
});
}
}

View File

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

View File

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

View File

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

View File

@@ -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<PrivacyRequestFactory.OnChainPrivacyGroup> groups =
node.execute(
transactions.findOnChainPrivacyGroup(
Base64String.unwrapList(expected.getMembers())));
assertThat(groups).contains(expected);
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String> {
private final Base64String privacyGroupId;
private final PrivacyNode adder;
private final List<String> 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);
}
}
}

View File

@@ -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<PrivxCreatePrivacyGroupResponse> {
private final PrivacyNode creator;
private final List<String> 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);
}
}
}

View File

@@ -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<List<PrivacyRequestFactory.OnChainPrivacyGroup>> {
private final List<Base64String> nodes;
public FindOnChainPrivacyGroupTransaction(final List<String> nodeEnclaveKeys) {
this.nodes = nodeEnclaveKeys.stream().map(Base64String::wrap).collect(Collectors.toList());
}
@Override
public List<PrivacyRequestFactory.OnChainPrivacyGroup> execute(final NodeRequests node) {
try {
return node.privacy().privxFindOnChainPrivacyGroup(nodes).send().getGroups();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

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

View File

@@ -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<String> nodes) {
return new FindPrivacyGroupTransaction(nodes);
}
public FindOnChainPrivacyGroupTransaction findOnChainPrivacyGroup(final List<String> nodes) {
return new FindOnChainPrivacyGroupTransaction(nodes);
}
public PrivDistributeTransactionTransaction privDistributeTransaction(
final String signedPrivateTransaction) {
return new PrivDistributeTransactionTransaction(signedPrivateTransaction);

View File

@@ -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<T extends Contract> implements Trans
Credentials.create(Accounts.GENESIS_ACCOUNT_ONE_PRIVATE_KEY);
private final Class<T> clazz;
private final Object[] args;
public DeploySmartContractTransaction(final Class<T> clazz) {
public DeploySmartContractTransaction(final Class<T> 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<Object> 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<Object> actualObjects) {
if (expectedTypes.length != actualObjects.size()) {
return false;
}
final ArrayList<Class> 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<T> cast(final Object invokedMethod) {
return (RemoteCall<T>) invokedMethod;

View File

@@ -26,6 +26,11 @@ public class ContractTransactions {
return new DeploySmartContractTransaction<>(clazz);
}
public <T extends Contract> DeploySmartContractTransaction<T> createSmartContract(
final Class<T> clazz, final Object... args) {
return new DeploySmartContractTransaction<>(clazz, args);
}
public CallSmartContractFunction callSmartContract(
final String functionName, final String contractAddress) {
return new CallSmartContractFunction(functionName, contractAddress);

View File

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

View File

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

View File

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

View File

@@ -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<Address> {}
@@ -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<String> 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<String> 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<?, PrivxFindPrivacyGroupResponse> privxFindOnChainPrivacyGroup(
final List<Base64String> nodes) {
return new Request<>(
"privx_findOnChainPrivacyGroup",
singletonList(nodes),
web3jService,
PrivxFindPrivacyGroupResponse.class);
}
public Request<?, GetPrivacyPrecompileAddressResponse> privGetPrivacyPrecompileAddress() {
return new Request<>(
"priv_getPrivacyPrecompileAddress",
@@ -179,4 +318,131 @@ public class PrivacyRequestFactory {
web3jService,
GetCodeResponse.class);
}
public static class PrivxFindPrivacyGroupResponse extends Response<List<OnChainPrivacyGroup>> {
public List<OnChainPrivacyGroup> getGroups() {
return getResult();
}
}
public static class OnChainPrivacyGroup {
private final Base64String privacyGroupId;
private final List<Base64String> 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<Base64String> members) {
this(privacyGroupId, members);
}
public OnChainPrivacyGroup(final String privacyGroupId, final List<Base64String> 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<Base64String> 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<Bytes> participants) {
return Bytes.concatenate(
OnChainGroupManagement.ADD_TO_GROUP_METHOD_SIGNATURE,
privateFrom,
encodeList(participants));
}
private Bytes encodeList(final List<Bytes> 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));
}
}

View File

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

View File

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

View File

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

View File

@@ -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<byte[]> 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<byte[]> 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();
}
}

View File

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

View File

@@ -15,6 +15,8 @@
<Logger name="org.hyperledger.besu.SubProcessLog" level="INFO" additivity="false">
<AppenderRef ref="SubProcessConsole" />
</Logger>
<Logger name="net.consensys.orion" level="OFF" additivity="false">
</Logger>
<Root level="${sys:root.log.level}">
<AppenderRef ref="Console" />
</Root>

View File

@@ -219,6 +219,9 @@ public abstract class BesuControllerBuilder<C> {
this::createConsensusContext);
validateContext(protocolContext);
protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor(
protocolContext.getWorldStateArchive());
final MutableBlockchain blockchain = protocolContext.getBlockchain();
Optional<Pruner> maybePruner = Optional.empty();

View File

@@ -82,6 +82,7 @@ public class PrivacyGroup implements Serializable {
public enum Type {
LEGACY,
ONCHAIN,
PANTHEON
}
}

View File

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

View File

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

View File

@@ -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<String> addPayloadEnclaveKey =
privacyController.buildAndSendAddPayload(
privateTransaction, enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
final String enclavePublicKey =
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser());
final ValidationResult<TransactionInvalidReason> 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<String> addPayloadEnclaveKey) {
return addPayloadEnclaveKey.isPresent()
? Bytes.concatenate(
Bytes.fromBase64String(enclaveKey),
Bytes.fromBase64String(addPayloadEnclaveKey.get()))
.toBase64String()
: enclaveKey;
}
}

View File

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

View File

@@ -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> 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<PrivateTransaction> 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<PrivateTransaction> 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<PrivateTransaction> fetchPayloadFromAddBlob(
final Bytes32 blockHash, final Hash expectedPrivacyMarkerTransactionHash) {
LOG.trace("Fetching transaction information from add blob");
final Optional<PrivacyGroupHeadBlockMap> privacyGroupHeadBlockMapOptional =
privateStateStorage.getPrivacyGroupHeadBlockMap(blockHash);
if (privacyGroupHeadBlockMapOptional.isPresent()) {
for (final Bytes32 privacyGroupId : privacyGroupHeadBlockMapOptional.get().keySet()) {
final Optional<Bytes32> addDataKey = privateStateStorage.getAddDataKey(privacyGroupId);
if (addDataKey.isPresent()) {
final List<PrivateTransactionWithMetadata> 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();
}
}

View File

@@ -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<PrivateTransaction> 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<PrivateTransaction> 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<PrivateTransaction> 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<PrivateTransaction> fetchPayloadFromAddBlob(
final Bytes32 blockHash, final Hash expectedPrivacyMarkerTransactionHash) {
LOG.trace("Fetching transaction information from add blob");
final Optional<PrivacyGroupHeadBlockMap> privacyGroupHeadBlockMapOptional =
privacyParameters.getPrivateStateStorage().getPrivacyGroupHeadBlockMap(blockHash);
if (privacyGroupHeadBlockMapOptional.isPresent()) {
for (final Bytes32 privacyGroupId : privacyGroupHeadBlockMapOptional.get().keySet()) {
final Optional<Bytes32> addDataKey =
privacyParameters.getPrivateStateStorage().getAddDataKey(privacyGroupId);
if (addDataKey.isPresent()) {
final List<PrivateTransactionWithMetadata> 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(

View File

@@ -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<PrivacyGroup> response =
privacyController.findOnChainPrivacyGroup(
Arrays.asList(addresses),
enclavePublicKeyProvider.getEnclaveKey(requestContext.getUser()));
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response);
}
}

View File

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

View File

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

View File

@@ -146,6 +146,7 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
final PrivateMarkerTransactionFactory markerTransactionFactory) {
final DefaultPrivacyController defaultPrivacyController =
new DefaultPrivacyController(
getBlockchainQueries().getBlockchain(),
privacyParameters,
protocolSchedule.getChainId(),
markerTransactionFactory,

View File

@@ -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<String, JsonRpcMethod> create(
final PrivacyController privacyController,
final EnclavePublicKeyProvider enclavePublicKeyProvider) {
return mapOf(new PrivxFindOnChainPrivacyGroup(privacyController, enclavePublicKeyProvider));
}
}

View File

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

View File

@@ -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<PrivacyGroup> result = (List<PrivacyGroup>) response.getResult();
assertThat(result).hasSize(1);
assertThat(result[0]).isEqualToComparingFieldByField(privacyGroup);
assertThat(result.get(0)).isEqualToComparingFieldByField(privacyGroup);
verify(privacyController).findPrivacyGroup(ADDRESSES, ENCLAVE_PUBLIC_KEY);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<C> implements ProtocolSchedule<C> {
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));
}
}

View File

@@ -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 <C> PrivacyBlockProcessor(
final BlockProcessor blockProcessor,
final ProtocolSchedule<C> 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<Transaction> transactions,
final List<BlockHeader> 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<PrivateTransactionWithMetadata> 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<PrivateTransactionWithMetadata> deserializeAddToGroupPayload(
final Bytes encodedAddToGroupPayload) {
final ArrayList<PrivateTransactionWithMetadata> 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;
}
}

View File

@@ -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<C> {
Optional<BigInteger> getChainId();
void setTransactionFilter(TransactionFilter transactionFilter);
void setPublicWorldStateArchiveForPrivacyBlockProcessor(
WorldStateArchive publicWorldStateArchive);
}

View File

@@ -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<C> {
@@ -288,4 +289,10 @@ public class ProtocolSpec<C> {
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);
}
}

View File

@@ -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<T> {
(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<T> blockValidator =

View File

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

View File

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

View File

@@ -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<BigInteger> 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<TransactionValidator.TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
@@ -185,6 +221,205 @@ public class DefaultPrivacyController implements PrivacyController {
return result;
}
@Override
public Optional<String> buildAndSendAddPayload(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
if (isGroupAdditionTransaction(privateTransaction)) {
final List<PrivateTransactionMetadata> privateTransactionMetadataList =
buildTransactionMetadataList(Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()));
if (privateTransactionMetadataList.size() > 0) {
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList =
retrievePrivateTransactions(
Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()),
privateTransactionMetadataList,
enclavePublicKey);
final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList);
final List<String> 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<PrivacyGroup> findOnChainPrivacyGroup(
final List<String> addresses, final String enclavePublicKey) {
final ArrayList<PrivacyGroup> privacyGroups = new ArrayList<>();
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
privacyGroupHeadBlockMap
.keySet()
.forEach(
c -> {
final List<String> participants = getExistingParticipants(c, enclavePublicKey);
if (participants.containsAll(addresses)) {
privacyGroups.add(
new PrivacyGroup(c.toBase64String(), Type.ONCHAIN, "", "", participants));
}
});
return privacyGroups;
}
private List<String> getExistingParticipants(
final Bytes privacyGroupId, final String enclavePublicKey) {
// get the privateFor list from the management contract
final Optional<PrivateTransactionProcessor.Result> privateTransactionSimulatorResultOptional =
privateTransactionSimulator.process(
privacyGroupId.toBase64String(),
buildCallParams(
Bytes.fromBase64String(enclavePublicKey), GET_PARTICIPANTS_METHOD_SIGNATURE));
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<String> decodeList(final Bytes rlpEncodedList) {
final ArrayList<String> decodedElements = new ArrayList<>();
// first 32 bytes is dynamic list offset
final UInt256 lengthOfList = UInt256.fromBytes(rlpEncodedList.slice(32, 32)); // length of list
for (int i = 0; i < lengthOfList.toLong(); ++i) {
decodedElements.add(
Bytes.wrap(rlpEncodedList.slice(64 + (32 * i), 32)).toBase64String()); // participant
}
return decodedElements;
}
private List<String> getParticipantsFromParameter(final Bytes input) {
final List<String> participants = new ArrayList<>();
final Bytes mungedParticipants = input.slice(4 + 32 + 32 + 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<PrivateTransactionMetadata> buildTransactionMetadataList(
final Bytes32 privacyGroupId) {
final List<PrivateTransactionMetadata> pmtHashes = new ArrayList<>();
PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) {
Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId);
while (blockHash != null) {
pmtHashes.addAll(
0,
privateStateStorage
.getPrivateBlockMetadata(blockHash, privacyGroupId)
.get()
.getPrivateTransactionMetadataList());
blockHash = blockchain.getBlockHeader(blockHash).get().getParentHash();
privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockHash)
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) {
blockHash = privacyGroupHeadBlockMap.get(privacyGroupId);
} else {
break;
}
}
}
return pmtHashes;
}
private List<PrivateTransactionWithMetadata> retrievePrivateTransactions(
final Bytes32 privacyGroupId,
final List<PrivateTransactionMetadata> privateTransactionMetadataList,
final String enclavePublicKey) {
final ArrayList<PrivateTransactionWithMetadata> privateTransactions = new ArrayList<>();
privateStateStorage
.getAddDataKey(privacyGroupId)
.ifPresent(key -> privateTransactions.addAll(retrieveAddBlob(key.toBase64String())));
for (int i = privateTransactions.size(); i < privateTransactionMetadataList.size(); i++) {
final PrivateTransactionMetadata privateTransactionMetadata =
privateTransactionMetadataList.get(i);
final Transaction privateMarkerTransaction =
blockchain
.getTransactionByHash(privateTransactionMetadata.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<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) {
final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey);
return deserializeAddToGroupPayload(
Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload())));
}
private Bytes serializeAddToGroupPayload(
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList) {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
rlpOutput.startList();
privateTransactionWithMetadataList.forEach(
privateTransactionWithMetadata -> privateTransactionWithMetadata.writeTo(rlpOutput));
rlpOutput.endList();
return rlpOutput.encoded();
}
private List<PrivateTransactionWithMetadata> deserializeAddToGroupPayload(
final Bytes encodedAddToGroupPayload) {
final ArrayList<PrivateTransactionWithMetadata> 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<Bytes> 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<String> 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<PrivateTransactionProcessor.Result> 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<String> privateFor =
return enclave.send(payload, privateTransaction.getPrivateFrom().toBase64String(), privateFor);
}
private List<String> resolvePrivateFor(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
final ArrayList<String> 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());
}
}

View File

@@ -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<TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction, final String enclavePublicKey) {
@@ -127,6 +141,39 @@ public class MultiTenancyPrivacyController implements PrivacyController {
privacyGroupId, enclavePublicKey, callParams, blockNumber);
}
@Override
public Optional<String> 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<PrivacyGroup> findOnChainPrivacyGroup(
final List<String> asList, final String enclaveKey) {
return privacyController.findOnChainPrivacyGroup(asList, enclaveKey);
}
@Override
public List<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) {
return privacyController.retrieveAddBlob(addDataKey);
}
@Override
public Optional<Bytes> getContractCode(
final String privacyGroupId,

View File

@@ -44,6 +44,11 @@ public interface PrivacyController {
Transaction createPrivacyMarkerTransaction(
String transactionEnclaveKey, PrivateTransaction privateTransaction);
Transaction createPrivacyMarkerTransaction(
String transactionEnclaveKey,
PrivateTransaction privateTransaction,
Address privacyPrecompileAddress);
ValidationResult<TransactionInvalidReason> validatePrivateTransaction(
PrivateTransaction privateTransaction, String enclavePublicKey);
@@ -58,9 +63,17 @@ public interface PrivacyController {
final CallParameter callParams,
final long blockNumber);
Optional<String> buildAndSendAddPayload(PrivateTransaction privateTransaction, String enclaveKey);
PrivacyGroup retrievePrivacyGroup(String toBase64String, String enclaveKey);
List<PrivacyGroup> findOnChainPrivacyGroup(List<String> asList, String enclaveKey);
Optional<Bytes> getContractCode(
final String privacyGroupId,
final Address contractAddress,
final Hash blockHash,
final String enclavePublicKey);
List<PrivateTransactionWithMetadata> retrieveAddBlob(String addDataKey);
}

View File

@@ -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<Hash, PrivateTransaction> forExecution,
final List<BlockHeader> ommers) {
long gasUsed = 0;
final List<TransactionReceipt> receipts = new ArrayList<>();
final List<Transaction> 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<BlockHeader> 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;
}
}

View File

@@ -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<PrivateTransactionWithMetadata> privateTransactionWithMetadataList) {
final long rehydrationStartTimestamp = System.currentTimeMillis();
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber();
final Optional<Bytes> 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<TransactionLocation> 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<Hash, PrivateTransaction> 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<Hash> 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<PrivateTransactionWithMetadata> privateTransactionWithMetadataList) {
return blockchain
.getBlockHeader(getBlockHashForIndex(index, privateTransactionWithMetadataList))
.orElseThrow()
.getNumber();
}
private Hash getBlockHashForIndex(
final int index,
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList) {
return blockchain
.getTransactionLocation(
privateTransactionWithMetadataList
.get(index)
.getPrivateTransactionMetadata()
.getPrivacyMarkerTransactionHash())
.orElseThrow()
.getBlockHash();
}
}

View File

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

View File

@@ -70,6 +70,12 @@ public class PrivateTransactionSimulator {
new PrivateStateRootResolver(privacyParameters.getPrivateStateStorage());
}
public Optional<PrivateTransactionProcessor.Result> process(
final String privacyGroupId, final CallParameter callParams) {
final BlockHeader header = blockchain.getChainHeadHeader();
return process(privacyGroupId, callParams, header);
}
public Optional<PrivateTransactionProcessor.Result> process(
final String privacyGroupId, final CallParameter callParams, final Hash blockHeaderHash) {
final BlockHeader header = blockchain.getBlockHeader(blockHeaderHash).orElse(null);

View File

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

View File

@@ -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<PrivateTransactionProcessor.Result> 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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,6 +31,8 @@ public interface PrivateStateStorage {
int getSchemaVersion();
Optional<Bytes32> 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();

View File

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

View File

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

View File

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

View File

@@ -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<Log> 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<TransactionInvalidReason> 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<String> 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<PrivacyGroup> 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<PrivateTransactionProcessor.Result> 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;

View File

@@ -232,7 +232,12 @@ public class MultiTenancyPrivacyControllerTest {
public void findsPrivacyGroupWhenEnclavePublicKeyInAddresses() {
final List<String> addresses = List.of(ENCLAVE_PUBLIC_KEY1, ENCLAVE_PUBLIC_KEY2);
final PrivacyGroup privacyGroup =
new PrivacyGroup(PRIVACY_GROUP_ID, Type.PANTHEON, "", "", List.of(ENCLAVE_PUBLIC_KEY2));
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});

View File

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

View File

@@ -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<C> implements ProtocolSchedule<C> {
public void setTransactionFilter(final TransactionFilter transactionFilter) {
delegate.setTransactionFilter(transactionFilter);
}
@Override
public void setPublicWorldStateArchiveForPrivacyBlockProcessor(
final WorldStateArchive publicWorldStateArchive) {
delegate.setPublicWorldStateArchiveForPrivacyBlockProcessor(publicWorldStateArchive);
}
}

View File

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

View File

@@ -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.
*
* <p><strong>Do not modify!</strong>
*
* <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the <a
* href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
*
* <p>Generated with web3j 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.<TypeReference<?>>asList(
new TypeReference<Bool>() {},
new TypeReference<Bytes32>() {},
new TypeReference<Utf8String>() {}));;
@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<ParticipantAddedEventResponse> getParticipantAddedEvents(
TransactionReceipt transactionReceipt) {
List<Contract.EventValuesWithLog> valueList =
extractEventParametersWithLog(PARTICIPANTADDED_EVENT, transactionReceipt);
ArrayList<ParticipantAddedEventResponse> responses =
new ArrayList<ParticipantAddedEventResponse>(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<ParticipantAddedEventResponse> participantAddedEventFlowable(EthFilter filter) {
return web3j
.ethLogFlowable(filter)
.map(
new Function<Log, ParticipantAddedEventResponse>() {
@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<ParticipantAddedEventResponse> participantAddedEventFlowable(
DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) {
EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
filter.addSingleTopic(EventEncoder.encode(PARTICIPANTADDED_EVENT));
return participantAddedEventFlowable(filter);
}
public RemoteFunctionCall<TransactionReceipt> addParticipants(
byte[] _enclaveKey, List<byte[]> _accounts) {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_ADDPARTICIPANTS,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey),
new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
org.web3j.abi.datatypes.generated.Bytes32.class,
org.web3j.abi.Utils.typeMap(
_accounts, org.web3j.abi.datatypes.generated.Bytes32.class))),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<Boolean> canExecute() {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_CANEXECUTE,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {}));
return executeRemoteCallSingleValueReturn(function, Boolean.class);
}
public RemoteFunctionCall<List> getParticipants(byte[] _enclaveKey) {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_GETPARTICIPANTS,
Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey)),
Arrays.<TypeReference<?>>asList(new TypeReference<DynamicArray<Bytes32>>() {}));
return new RemoteFunctionCall<List>(
function,
new Callable<List>() {
@Override
@SuppressWarnings("unchecked")
public List call() throws Exception {
List<Type> result = (List<Type>) executeCallSingleValueReturn(function, List.class);
return convertToNative(result);
}
});
}
public RemoteFunctionCall<BigInteger> getVersion() {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_GETVERSION,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Int256>() {}));
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
}
public RemoteFunctionCall<TransactionReceipt> lock() {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_LOCK, Arrays.<Type>asList(), Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<TransactionReceipt> removeParticipant(
byte[] _enclaveKey, byte[] _account) {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_REMOVEPARTICIPANT,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(_enclaveKey),
new org.web3j.abi.datatypes.generated.Bytes32(_account)),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<TransactionReceipt> unlock() {
final org.web3j.abi.datatypes.Function function =
new org.web3j.abi.datatypes.Function(
FUNC_UNLOCK, Arrays.<Type>asList(), Collections.<TypeReference<?>>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<PrivacyGroup> deploy(
Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
PrivacyGroup.class, web3j, credentials, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<PrivacyGroup> deploy(
Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(PrivacyGroup.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
}
public static RemoteCall<PrivacyGroup> deploy(
Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
PrivacyGroup.class, web3j, transactionManager, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<PrivacyGroup> 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;
}
}

View File

@@ -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.
*
* <p><strong>Do not modify!</strong>
*
* <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the <a
* href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
*
* <p>Generated with web3j version 4.5.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<TransactionReceipt> addParticipants(
byte[] enclaveKey, List<byte[]> participants) {
final Function function =
new Function(
FUNC_ADDPARTICIPANTS,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey),
new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Bytes32>(
org.web3j.abi.datatypes.generated.Bytes32.class,
org.web3j.abi.Utils.typeMap(
participants, org.web3j.abi.datatypes.generated.Bytes32.class))),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<Boolean> canExecute() {
final Function function =
new Function(
FUNC_CANEXECUTE,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() {}));
return executeRemoteCallSingleValueReturn(function, Boolean.class);
}
public RemoteFunctionCall<List> getParticipants(byte[] enclaveKey) {
final Function function =
new Function(
FUNC_GETPARTICIPANTS,
Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey)),
Arrays.<TypeReference<?>>asList(new TypeReference<DynamicArray<Bytes32>>() {}));
return new RemoteFunctionCall<List>(
function,
new Callable<List>() {
@Override
@SuppressWarnings("unchecked")
public List call() throws Exception {
List<Type> result = (List<Type>) executeCallSingleValueReturn(function, List.class);
return convertToNative(result);
}
});
}
public RemoteFunctionCall<BigInteger> getVersion() {
final Function function =
new Function(
FUNC_GETVERSION,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Int256>() {}));
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
}
public RemoteFunctionCall<TransactionReceipt> lock() {
final Function function =
new Function(FUNC_LOCK, Arrays.<Type>asList(), Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<TransactionReceipt> removeParticipant(
byte[] enclaveKey, byte[] account) {
final Function function =
new Function(
FUNC_REMOVEPARTICIPANT,
Arrays.<Type>asList(
new org.web3j.abi.datatypes.generated.Bytes32(enclaveKey),
new org.web3j.abi.datatypes.generated.Bytes32(account)),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}
public RemoteFunctionCall<TransactionReceipt> unlock() {
final Function function =
new Function(FUNC_UNLOCK, Arrays.<Type>asList(), Collections.<TypeReference<?>>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<PrivacyInterface> deploy(
Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
PrivacyInterface.class, web3j, credentials, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<PrivacyInterface> deploy(
Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(
PrivacyInterface.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
}
public static RemoteCall<PrivacyInterface> deploy(
Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return deployRemoteCall(
PrivacyInterface.class, web3j, transactionManager, contractGasProvider, BINARY, "");
}
@Deprecated
public static RemoteCall<PrivacyInterface> deploy(
Web3j web3j,
TransactionManager transactionManager,
BigInteger gasPrice,
BigInteger gasLimit) {
return deployRemoteCall(
PrivacyInterface.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, "");
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,3 +47,4 @@ include 'services:pipeline'
include 'services:tasks'
include 'testutil'
include 'util'
include 'privacy-contracts'