mirror of https://github.com/hyperledger/besu
Refactor privacy controller (#2971)
* refactor of PrivacyController Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>pull/3039/head
parent
2f1871852e
commit
4d72369469
@ -0,0 +1,122 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.PrivacyParameters; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; |
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
|
||||
public abstract class AbstractPrivacyController implements PrivacyController { |
||||
|
||||
final Blockchain blockchain; |
||||
final PrivateStateStorage privateStateStorage; |
||||
final PrivateTransactionValidator privateTransactionValidator; |
||||
final PrivateTransactionSimulator privateTransactionSimulator; |
||||
final PrivateNonceProvider privateNonceProvider; |
||||
final PrivateWorldStateReader privateWorldStateReader; |
||||
final PrivateStateRootResolver privateStateRootResolver; |
||||
|
||||
protected AbstractPrivacyController( |
||||
final Blockchain blockchain, |
||||
final PrivacyParameters privacyParameters, |
||||
final Optional<BigInteger> chainId, |
||||
final PrivateTransactionSimulator privateTransactionSimulator, |
||||
final PrivateNonceProvider privateNonceProvider, |
||||
final PrivateWorldStateReader privateWorldStateReader) { |
||||
this( |
||||
blockchain, |
||||
privacyParameters.getPrivateStateStorage(), |
||||
new PrivateTransactionValidator(chainId), |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privacyParameters.getPrivateStateRootResolver()); |
||||
} |
||||
|
||||
protected AbstractPrivacyController( |
||||
final Blockchain blockchain, |
||||
final PrivateStateStorage privateStateStorage, |
||||
final PrivateTransactionValidator privateTransactionValidator, |
||||
final PrivateTransactionSimulator privateTransactionSimulator, |
||||
final PrivateNonceProvider privateNonceProvider, |
||||
final PrivateWorldStateReader privateWorldStateReader, |
||||
final PrivateStateRootResolver privateStateRootResolver) { |
||||
this.blockchain = blockchain; |
||||
this.privateStateStorage = privateStateStorage; |
||||
this.privateTransactionValidator = privateTransactionValidator; |
||||
this.privateTransactionSimulator = privateTransactionSimulator; |
||||
this.privateNonceProvider = privateNonceProvider; |
||||
this.privateWorldStateReader = privateWorldStateReader; |
||||
this.privateStateRootResolver = privateStateRootResolver; |
||||
} |
||||
|
||||
@Override |
||||
public ValidationResult<TransactionInvalidReason> validatePrivateTransaction( |
||||
final PrivateTransaction privateTransaction, final String privacyUserId) { |
||||
final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); |
||||
return privateTransactionValidator.validate( |
||||
privateTransaction, |
||||
determineNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), |
||||
true); |
||||
} |
||||
|
||||
@Override |
||||
public long determineNonce( |
||||
final Address sender, final String privacyGroupId, final String privacyUserId) { |
||||
return privateNonceProvider.getNonce( |
||||
sender, Bytes32.wrap(Bytes.fromBase64String(privacyGroupId))); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<TransactionProcessingResult> simulatePrivateTransaction( |
||||
final String privacyGroupId, |
||||
final String privacyUserId, |
||||
final CallParameter callParams, |
||||
final long blockNumber) { |
||||
return privateTransactionSimulator.process(privacyGroupId, callParams, blockNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getContractCode( |
||||
final String privacyGroupId, |
||||
final Address contractAddress, |
||||
final Hash blockHash, |
||||
final String privacyUserId) { |
||||
return privateWorldStateReader.getContractCode(privacyGroupId, blockHash, contractAddress); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getStateRootByBlockNumber( |
||||
final String privacyGroupId, final String privacyUserId, final long blockNumber) { |
||||
return blockchain |
||||
.getBlockByNumber(blockNumber) |
||||
.map( |
||||
block -> |
||||
privateStateRootResolver.resolveLastStateRoot( |
||||
Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)), block.getHash())); |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.datatypes.Hash; |
||||
import org.hyperledger.besu.enclave.Enclave; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public abstract class AbstractRestrictedPrivacyController extends AbstractPrivacyController { |
||||
|
||||
final Enclave enclave; |
||||
final PrivateTransactionLocator privateTransactionLocator; |
||||
|
||||
protected AbstractRestrictedPrivacyController( |
||||
final Blockchain blockchain, |
||||
final PrivateStateStorage privateStateStorage, |
||||
final Enclave enclave, |
||||
final PrivateTransactionValidator privateTransactionValidator, |
||||
final PrivateTransactionSimulator privateTransactionSimulator, |
||||
final PrivateNonceProvider privateNonceProvider, |
||||
final PrivateWorldStateReader privateWorldStateReader, |
||||
final PrivateStateRootResolver privateStateRootResolver) { |
||||
super( |
||||
blockchain, |
||||
privateStateStorage, |
||||
privateTransactionValidator, |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privateStateRootResolver); |
||||
this.enclave = enclave; |
||||
this.privateTransactionLocator = |
||||
new PrivateTransactionLocator(blockchain, enclave, privateStateStorage); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash( |
||||
final Hash pmtHash, final String enclaveKey) { |
||||
return privateTransactionLocator.findByPmtHash(pmtHash, enclaveKey); |
||||
} |
||||
} |
@ -0,0 +1,165 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class MultiTenancyPrivacyController implements PrivacyController { |
||||
|
||||
private final PrivacyController privacyController; |
||||
|
||||
public MultiTenancyPrivacyController(final PrivacyController privacyController) { |
||||
this.privacyController = privacyController; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash( |
||||
final Hash pmtHash, final String enclaveKey) { |
||||
return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey); |
||||
} |
||||
|
||||
@Override |
||||
public String createPrivateMarkerTransactionPayload( |
||||
final PrivateTransaction privateTransaction, |
||||
final String privacyUserId, |
||||
final Optional<PrivacyGroup> maybePrivacyGroup) { |
||||
final Optional<Bytes> maybePrivacyGroupId = privateTransaction.getPrivacyGroupId(); |
||||
if (maybePrivacyGroupId.isPresent()) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
maybePrivacyGroupId.get().toBase64String(), privacyUserId); |
||||
} |
||||
return privacyController.createPrivateMarkerTransactionPayload( |
||||
privateTransaction, privacyUserId, maybePrivacyGroup); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup createPrivacyGroup( |
||||
final List<String> addresses, |
||||
final String name, |
||||
final String description, |
||||
final String privacyUserId) { |
||||
if (!addresses.contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group addresses must contain the enclave public key"); |
||||
} |
||||
return privacyController.createPrivacyGroup(addresses, name, description, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.deletePrivacyGroup(privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup[] findPrivacyGroupByMembers( |
||||
final List<String> addresses, final String privacyUserId) { |
||||
if (!addresses.contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group addresses must contain the enclave public key"); |
||||
} |
||||
return privacyController.findPrivacyGroupByMembers(addresses, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public ValidationResult<TransactionInvalidReason> validatePrivateTransaction( |
||||
final PrivateTransaction privateTransaction, final String privacyUserId) { |
||||
|
||||
final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.validatePrivateTransaction(privateTransaction, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public long determineNonce( |
||||
final Address sender, final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.determineNonce(sender, privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<TransactionProcessingResult> simulatePrivateTransaction( |
||||
final String privacyGroupId, |
||||
final String privacyUserId, |
||||
final CallParameter callParams, |
||||
final long blockNumber) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privacyGroupId, privacyUserId, Optional.of(blockNumber)); |
||||
return privacyController.simulatePrivateTransaction( |
||||
privacyGroupId, privacyUserId, callParams, blockNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<PrivacyGroup> findPrivacyGroupByGroupId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId); |
||||
checkGroupParticipation(maybePrivacyGroup, privacyUserId); |
||||
return maybePrivacyGroup; |
||||
} |
||||
|
||||
private void checkGroupParticipation( |
||||
final Optional<PrivacyGroup> maybePrivacyGroup, final String enclaveKey) { |
||||
if (maybePrivacyGroup.isPresent() |
||||
&& !maybePrivacyGroup.get().getMembers().contains(enclaveKey)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group must contain the enclave public key"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getContractCode( |
||||
final String privacyGroupId, |
||||
final Address contractAddress, |
||||
final Hash blockHash, |
||||
final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.getContractCode( |
||||
privacyGroupId, contractAddress, blockHash, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
privacyController.verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId, final Optional<Long> blockNumber) |
||||
throws MultiTenancyValidationException { |
||||
privacyController.verifyPrivacyGroupContainsPrivacyUserId( |
||||
privacyGroupId, privacyUserId, blockNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getStateRootByBlockNumber( |
||||
final String privacyGroupId, final String privacyUserId, final long blockNumber) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privacyGroupId, privacyUserId, Optional.of(blockNumber)); |
||||
return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber); |
||||
} |
||||
} |
@ -0,0 +1,364 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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 java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.hyperledger.besu.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY_PROXY; |
||||
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.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.enclave.Enclave; |
||||
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||
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.PrivacyParameters; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
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.processing.TransactionProcessingResult; |
||||
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.List; |
||||
import java.util.Optional; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
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 OnchainPrivacyController extends AbstractRestrictedPrivacyController { |
||||
|
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
|
||||
private OnchainPrivacyGroupContract onchainPrivacyGroupContract; |
||||
|
||||
public OnchainPrivacyController( |
||||
final Blockchain blockchain, |
||||
final PrivacyParameters privacyParameters, |
||||
final Optional<BigInteger> chainId, |
||||
final PrivateTransactionSimulator privateTransactionSimulator, |
||||
final PrivateNonceProvider privateNonceProvider, |
||||
final PrivateWorldStateReader privateWorldStateReader) { |
||||
this( |
||||
blockchain, |
||||
privacyParameters.getPrivateStateStorage(), |
||||
privacyParameters.getEnclave(), |
||||
new PrivateTransactionValidator(chainId), |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privacyParameters.getPrivateStateRootResolver()); |
||||
} |
||||
|
||||
public OnchainPrivacyController( |
||||
final Blockchain blockchain, |
||||
final PrivateStateStorage privateStateStorage, |
||||
final Enclave enclave, |
||||
final PrivateTransactionValidator privateTransactionValidator, |
||||
final PrivateTransactionSimulator privateTransactionSimulator, |
||||
final PrivateNonceProvider privateNonceProvider, |
||||
final PrivateWorldStateReader privateWorldStateReader, |
||||
final PrivateStateRootResolver privateStateRootResolver) { |
||||
super( |
||||
blockchain, |
||||
privateStateStorage, |
||||
enclave, |
||||
privateTransactionValidator, |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privateStateRootResolver); |
||||
|
||||
onchainPrivacyGroupContract = new OnchainPrivacyGroupContract(privateTransactionSimulator); |
||||
} |
||||
|
||||
@Override |
||||
public String createPrivateMarkerTransactionPayload( |
||||
final PrivateTransaction privateTransaction, |
||||
final String privacyUserId, |
||||
final Optional<PrivacyGroup> privacyGroup) { |
||||
final String firstPart; |
||||
try { |
||||
LOG.trace("Storing private transaction in enclave"); |
||||
final SendResponse sendResponse = sendRequest(privateTransaction, privacyGroup); |
||||
firstPart = sendResponse.getKey(); |
||||
} catch (final Exception e) { |
||||
LOG.error("Failed to store private transaction in enclave", e); |
||||
throw e; |
||||
} |
||||
final Optional<String> optionalSecondPart = |
||||
buildAndSendAddPayload( |
||||
privateTransaction, |
||||
Bytes32.wrap(privateTransaction.getPrivacyGroupId().orElseThrow()), |
||||
privacyUserId); |
||||
|
||||
return buildCompoundLookupId(firstPart, optionalSecondPart); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<PrivacyGroup> findPrivacyGroupByGroupId( |
||||
final String privacyGroupId, final String enclaveKey) { |
||||
// get the privateFor list from the management contract
|
||||
final Optional<TransactionProcessingResult> privateTransactionSimulatorResultOptional = |
||||
privateTransactionSimulator.process( |
||||
privacyGroupId, buildCallParams(GET_PARTICIPANTS_METHOD_SIGNATURE)); |
||||
|
||||
if (privateTransactionSimulatorResultOptional.isPresent() |
||||
&& privateTransactionSimulatorResultOptional.get().isSuccessful()) { |
||||
final RLPInput rlpInput = |
||||
RLP.input(privateTransactionSimulatorResultOptional.get().getOutput()); |
||||
if (rlpInput.nextSize() > 0) { |
||||
return Optional.of( |
||||
new PrivacyGroup( |
||||
privacyGroupId, |
||||
PrivacyGroup.Type.ONCHAIN, |
||||
"", |
||||
"", |
||||
decodeParticipantList(rlpInput.raw()))); |
||||
} |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup[] findPrivacyGroupByMembers( |
||||
final List<String> addresses, final String privacyUserId) { |
||||
final ArrayList<PrivacyGroup> privacyGroups = new ArrayList<>(); |
||||
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = |
||||
privateStateStorage |
||||
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) |
||||
.orElse(PrivacyGroupHeadBlockMap.empty()); |
||||
privacyGroupHeadBlockMap |
||||
.keySet() |
||||
.forEach( |
||||
c -> { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
findPrivacyGroupByGroupId(c.toBase64String(), privacyUserId); |
||||
if (maybePrivacyGroup.isPresent() |
||||
&& maybePrivacyGroup.get().getMembers().containsAll(addresses)) { |
||||
privacyGroups.add(maybePrivacyGroup.get()); |
||||
} |
||||
}); |
||||
return privacyGroups.toArray(new PrivacyGroup[0]); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup createPrivacyGroup( |
||||
final List<String> addresses, |
||||
final String name, |
||||
final String description, |
||||
final String privacyUserId) { |
||||
throw new PrivacyConfigurationNotSupportedException( |
||||
"Method not supported when using onchain privacy"); |
||||
} |
||||
|
||||
@Override |
||||
public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { |
||||
throw new PrivacyConfigurationNotSupportedException( |
||||
"Method not supported when using onchain privacy"); |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId, Optional.empty()); |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId, final Optional<Long> blockNumber) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
onchainPrivacyGroupContract.getPrivacyGroupByIdAndBlockNumber(privacyGroupId, blockNumber); |
||||
// IF the group exists, check member
|
||||
// ELSE member is valid if the group doesn't exist yet - this is normal for onchain privacy
|
||||
// groups
|
||||
maybePrivacyGroup.ifPresent( |
||||
group -> { |
||||
if (!group.getMembers().contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group must contain the enclave public key"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private List<String> decodeParticipantList(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<PrivateTransactionMetadata> buildTransactionMetadataList( |
||||
final Bytes privacyGroupId) { |
||||
final List<PrivateTransactionMetadata> pmtHashes = new ArrayList<>(); |
||||
PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = |
||||
privateStateStorage |
||||
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash()) |
||||
.orElse(PrivacyGroupHeadBlockMap.empty()); |
||||
if (privacyGroupHeadBlockMap.containsKey(privacyGroupId)) { |
||||
Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); |
||||
while (blockHash != null) { |
||||
pmtHashes.addAll( |
||||
0, |
||||
privateStateStorage |
||||
.getPrivateBlockMetadata(blockHash, Bytes32.wrap(privacyGroupId)) |
||||
.orElseThrow() |
||||
.getPrivateTransactionMetadataList()); |
||||
blockHash = blockchain.getBlockHeader(blockHash).orElseThrow().getParentHash(); |
||||
privacyGroupHeadBlockMap = |
||||
privateStateStorage |
||||
.getPrivacyGroupHeadBlockMap(blockHash) |
||||
.orElse(PrivacyGroupHeadBlockMap.empty()); |
||||
if (privacyGroupHeadBlockMap.containsKey(privacyGroupId)) { |
||||
blockHash = privacyGroupHeadBlockMap.get(privacyGroupId); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return pmtHashes; |
||||
} |
||||
|
||||
private List<PrivateTransactionWithMetadata> retrievePrivateTransactions( |
||||
final Bytes32 privacyGroupId, |
||||
final List<PrivateTransactionMetadata> privateTransactionMetadataList, |
||||
final String privacyUserId) { |
||||
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.getPrivateMarkerTransactionHash()) |
||||
.orElseThrow(); |
||||
final ReceiveResponse receiveResponse = |
||||
retrieveTransaction( |
||||
privateMarkerTransaction.getPayload().slice(0, 32).toBase64String(), privacyUserId); |
||||
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; |
||||
} |
||||
|
||||
private List<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) { |
||||
final ReceiveResponse addReceiveResponse = enclave.receive(addDataKey); |
||||
return PrivateTransactionWithMetadata.readListFromPayload( |
||||
Bytes.wrap(Base64.getDecoder().decode(addReceiveResponse.getPayload()))); |
||||
} |
||||
|
||||
private Optional<String> buildAndSendAddPayload( |
||||
final PrivateTransaction privateTransaction, |
||||
final Bytes32 privacyGroupId, |
||||
final String privacyUserId) { |
||||
if (OnchainUtil.isGroupAdditionTransaction(privateTransaction)) { |
||||
final List<PrivateTransactionMetadata> privateTransactionMetadataList = |
||||
buildTransactionMetadataList(privacyGroupId); |
||||
if (!privateTransactionMetadataList.isEmpty()) { |
||||
final List<PrivateTransactionWithMetadata> privateTransactionWithMetadataList = |
||||
retrievePrivateTransactions( |
||||
privacyGroupId, privateTransactionMetadataList, privacyUserId); |
||||
final Bytes bytes = serializeAddToGroupPayload(privateTransactionWithMetadataList); |
||||
final List<String> privateFor = |
||||
OnchainUtil.getParticipantsFromParameter(privateTransaction.getPayload()); |
||||
return Optional.of( |
||||
enclave.send(bytes.toBase64String(), privacyUserId, privateFor).getKey()); |
||||
} |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
private String buildCompoundLookupId( |
||||
final String privateTransactionLookupId, |
||||
final Optional<String> maybePrivateTransactionLookupId) { |
||||
return maybePrivateTransactionLookupId.isPresent() |
||||
? Bytes.concatenate( |
||||
Bytes.fromBase64String(privateTransactionLookupId), |
||||
Bytes.fromBase64String(maybePrivateTransactionLookupId.get())) |
||||
.toBase64String() |
||||
: privateTransactionLookupId; |
||||
} |
||||
|
||||
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 SendResponse sendRequest( |
||||
final PrivateTransaction privateTransaction, final Optional<PrivacyGroup> maybePrivacyGroup) { |
||||
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); |
||||
|
||||
final PrivacyGroup privacyGroup = maybePrivacyGroup.orElseThrow(); |
||||
final Optional<TransactionProcessingResult> version = |
||||
privateTransactionSimulator.process( |
||||
privateTransaction.getPrivacyGroupId().orElseThrow().toBase64String(), |
||||
buildCallParams(GET_VERSION_METHOD_SIGNATURE)); |
||||
new VersionedPrivateTransaction(privateTransaction, version).writeTo(rlpOutput); |
||||
final List<String> onchainPrivateFor = privacyGroup.getMembers(); |
||||
return enclave.send( |
||||
rlpOutput.encoded().toBase64String(), |
||||
privateTransaction.getPrivateFrom().toBase64String(), |
||||
onchainPrivateFor); |
||||
} |
||||
|
||||
CallParameter buildCallParams(final Bytes methodCall) { |
||||
return new CallParameter( |
||||
Address.ZERO, ONCHAIN_PRIVACY_PROXY, 3000000, Wei.of(1000), Wei.ZERO, methodCall); |
||||
} |
||||
|
||||
ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { |
||||
return enclave.receive(enclaveKey, privacyUserId); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
public void setOnchainPrivacyGroupContract( |
||||
final OnchainPrivacyGroupContract onchainPrivacyGroupContract) { |
||||
this.onchainPrivacyGroupContract = onchainPrivacyGroupContract; |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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.ethereum.core.PrivacyParameters.ONCHAIN_PRIVACY_PROXY; |
||||
import static org.hyperledger.besu.ethereum.privacy.group.OnchainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class OnchainUtil { |
||||
|
||||
private OnchainUtil() {} |
||||
|
||||
public static boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { |
||||
final Optional<Address> to = privateTransaction.getTo(); |
||||
return to.isPresent() |
||||
&& to.get().equals(ONCHAIN_PRIVACY_PROXY) |
||||
&& privateTransaction |
||||
.getPayload() |
||||
.toHexString() |
||||
.startsWith(ADD_PARTICIPANTS_METHOD_SIGNATURE.toHexString()); |
||||
} |
||||
|
||||
public static List<String> getParticipantsFromParameter(final Bytes input) { |
||||
final List<String> participants = new ArrayList<>(); |
||||
final Bytes mungedParticipants = input.slice(4 + 32 + 32); |
||||
for (int i = 0; i <= mungedParticipants.size() - 32; i += 32) { |
||||
participants.add(mungedParticipants.slice(i, 32).toBase64String()); |
||||
} |
||||
return participants; |
||||
} |
||||
} |
@ -1,326 +0,0 @@ |
||||
/* |
||||
* 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.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.enclave.Enclave; |
||||
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||
import org.hyperledger.besu.enclave.types.ReceiveResponse; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
|
||||
public class RestrictedMultiTenancyPrivacyController implements PrivacyController { |
||||
|
||||
private final PrivacyController privacyController; |
||||
private final Enclave enclave; |
||||
private final PrivateTransactionValidator privateTransactionValidator; |
||||
private final Optional<OnchainPrivacyGroupContract> onchainPrivacyGroupContract; |
||||
|
||||
public RestrictedMultiTenancyPrivacyController( |
||||
final PrivacyController privacyController, |
||||
final Optional<BigInteger> chainId, |
||||
final Enclave enclave, |
||||
final boolean onchainPrivacyGroupsEnabled) { |
||||
this.privacyController = privacyController; |
||||
this.enclave = enclave; |
||||
this.onchainPrivacyGroupContract = |
||||
onchainPrivacyGroupsEnabled |
||||
? Optional.of( |
||||
new OnchainPrivacyGroupContract(privacyController.getTransactionSimulator())) |
||||
: Optional.empty(); |
||||
privateTransactionValidator = new PrivateTransactionValidator(chainId); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
RestrictedMultiTenancyPrivacyController( |
||||
final PrivacyController privacyController, |
||||
final Optional<BigInteger> chainId, |
||||
final Enclave enclave, |
||||
final Optional<OnchainPrivacyGroupContract> onchainPrivacyGroupContract) { |
||||
this.privacyController = privacyController; |
||||
this.enclave = enclave; |
||||
this.onchainPrivacyGroupContract = onchainPrivacyGroupContract; |
||||
privateTransactionValidator = new PrivateTransactionValidator(chainId); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<ExecutedPrivateTransaction> findPrivateTransactionByPmtHash( |
||||
final Hash pmtHash, final String enclaveKey) { |
||||
return privacyController.findPrivateTransactionByPmtHash(pmtHash, enclaveKey); |
||||
} |
||||
|
||||
@Override |
||||
public String createPrivateMarkerTransactionPayload( |
||||
final PrivateTransaction privateTransaction, |
||||
final String privacyUserId, |
||||
final Optional<PrivacyGroup> maybePrivacyGroup) { |
||||
verifyPrivateFromMatchesPrivacyUserId( |
||||
privateTransaction.getPrivateFrom().toBase64String(), privacyUserId); |
||||
if (privateTransaction.getPrivacyGroupId().isPresent()) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privateTransaction.getPrivacyGroupId().get().toBase64String(), privacyUserId); |
||||
} |
||||
return privacyController.createPrivateMarkerTransactionPayload( |
||||
privateTransaction, privacyUserId, maybePrivacyGroup); |
||||
} |
||||
|
||||
@Override |
||||
public ReceiveResponse retrieveTransaction(final String enclaveKey, final String privacyUserId) { |
||||
// no validation necessary as the enclave receive only returns data for the enclave public key
|
||||
return privacyController.retrieveTransaction(enclaveKey, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup createPrivacyGroup( |
||||
final List<String> addresses, |
||||
final String name, |
||||
final String description, |
||||
final String privacyUserId) { |
||||
// no validation necessary as the enclave createPrivacyGroup fails if the addresses don't
|
||||
// include the from (privacyUserId)
|
||||
return privacyController.createPrivacyGroup(addresses, name, description, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public String deletePrivacyGroup(final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.deletePrivacyGroup(privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public PrivacyGroup[] findOffchainPrivacyGroupByMembers( |
||||
final List<String> addresses, final String privacyUserId) { |
||||
if (!addresses.contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group addresses must contain the enclave public key"); |
||||
} |
||||
final PrivacyGroup[] resultantGroups = |
||||
privacyController.findOffchainPrivacyGroupByMembers(addresses, privacyUserId); |
||||
return Arrays.stream(resultantGroups) |
||||
.filter(g -> g.getMembers().contains(privacyUserId)) |
||||
.toArray(PrivacyGroup[]::new); |
||||
} |
||||
|
||||
@Override |
||||
public ValidationResult<TransactionInvalidReason> validatePrivateTransaction( |
||||
final PrivateTransaction privateTransaction, final String privacyUserId) { |
||||
|
||||
final String privacyGroupId = privateTransaction.determinePrivacyGroupId().toBase64String(); |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privateTransactionValidator.validate( |
||||
privateTransaction, |
||||
determineBesuNonce(privateTransaction.getSender(), privacyGroupId, privacyUserId), |
||||
true); |
||||
} |
||||
|
||||
@Override |
||||
public long determineEeaNonce( |
||||
final String privateFrom, |
||||
final String[] privateFor, |
||||
final Address address, |
||||
final String privacyUserId) { |
||||
verifyPrivateFromMatchesPrivacyUserId(privateFrom, privacyUserId); |
||||
return privacyController.determineEeaNonce(privateFrom, privateFor, address, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public long determineBesuNonce( |
||||
final Address sender, final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.determineBesuNonce(sender, privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<TransactionProcessingResult> simulatePrivateTransaction( |
||||
final String privacyGroupId, |
||||
final String privacyUserId, |
||||
final CallParameter callParams, |
||||
final long blockNumber) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privacyGroupId, privacyUserId, Optional.of(blockNumber)); |
||||
return privacyController.simulatePrivateTransaction( |
||||
privacyGroupId, privacyUserId, callParams, blockNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<String> buildAndSendAddPayload( |
||||
final PrivateTransaction privateTransaction, |
||||
final Bytes32 privacyGroupId, |
||||
final String privacyUserId) { |
||||
verifyPrivateFromMatchesPrivacyUserId( |
||||
privateTransaction.getPrivateFrom().toBase64String(), privacyUserId); |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privateTransaction.getPrivacyGroupId().get().toBase64String(), privacyUserId); |
||||
return privacyController.buildAndSendAddPayload( |
||||
privateTransaction, privacyGroupId, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<PrivacyGroup> findOffchainPrivacyGroupByGroupId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
privacyController.findOffchainPrivacyGroupByGroupId(privacyGroupId, privacyUserId); |
||||
checkGroupParticipation(maybePrivacyGroup, privacyUserId); |
||||
return maybePrivacyGroup; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<PrivacyGroup> findPrivacyGroupByGroupId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
privacyController.findPrivacyGroupByGroupId(privacyGroupId, privacyUserId); |
||||
checkGroupParticipation(maybePrivacyGroup, privacyUserId); |
||||
return maybePrivacyGroup; |
||||
} |
||||
|
||||
private void checkGroupParticipation( |
||||
final Optional<PrivacyGroup> maybePrivacyGroup, final String enclaveKey) { |
||||
if (maybePrivacyGroup.isPresent() |
||||
&& !maybePrivacyGroup.get().getMembers().contains(enclaveKey)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group must contain the enclave public key"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public List<PrivacyGroup> findOnchainPrivacyGroupByMembers( |
||||
final List<String> addresses, final String privacyUserId) { |
||||
if (!addresses.contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group addresses must contain the enclave public key"); |
||||
} |
||||
final List<PrivacyGroup> resultantGroups = |
||||
privacyController.findOnchainPrivacyGroupByMembers(addresses, privacyUserId); |
||||
return resultantGroups.stream() |
||||
.filter(g -> g.getMembers().contains(privacyUserId)) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
@Override |
||||
public List<PrivateTransactionWithMetadata> retrieveAddBlob(final String addDataKey) { |
||||
return privacyController.retrieveAddBlob(addDataKey); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isGroupAdditionTransaction(final PrivateTransaction privateTransaction) { |
||||
return privacyController.isGroupAdditionTransaction(privateTransaction); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getContractCode( |
||||
final String privacyGroupId, |
||||
final Address contractAddress, |
||||
final Hash blockHash, |
||||
final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); |
||||
return privacyController.getContractCode( |
||||
privacyGroupId, contractAddress, blockHash, privacyUserId); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<PrivacyGroup> findOnchainPrivacyGroupAndAddNewMembers( |
||||
final Bytes privacyGroupId, |
||||
final String privacyUserId, |
||||
final PrivateTransaction privateTransaction) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
privacyController.findOnchainPrivacyGroupAndAddNewMembers( |
||||
privacyGroupId, privacyUserId, privateTransaction); |
||||
// The check that the privacyUserId is a member (if the group already exists) is done in the
|
||||
// DefaultPrivacyController.
|
||||
return maybePrivacyGroup; |
||||
} |
||||
|
||||
private void verifyPrivateFromMatchesPrivacyUserId( |
||||
final String privateFrom, final String privacyUserId) { |
||||
if (!privateFrom.equals(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Transaction privateFrom must match enclave public key"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId, Optional.empty()); |
||||
} |
||||
|
||||
@Override |
||||
public void verifyPrivacyGroupContainsPrivacyUserId( |
||||
final String privacyGroupId, final String privacyUserId, final Optional<Long> blockNumber) |
||||
throws MultiTenancyValidationException { |
||||
onchainPrivacyGroupContract.ifPresentOrElse( |
||||
(contract) -> { |
||||
verifyOnchainPrivacyGroupContainsMember( |
||||
contract, privacyGroupId, privacyUserId, blockNumber); |
||||
}, |
||||
() -> { |
||||
verifyOffchainPrivacyGroupContainsMember(privacyGroupId, privacyUserId); |
||||
}); |
||||
} |
||||
|
||||
private void verifyOffchainPrivacyGroupContainsMember( |
||||
final String privacyGroupId, final String privacyUserId) { |
||||
final PrivacyGroup offchainPrivacyGroup = enclave.retrievePrivacyGroup(privacyGroupId); |
||||
if (!offchainPrivacyGroup.getMembers().contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group must contain the enclave public key"); |
||||
} |
||||
} |
||||
|
||||
private void verifyOnchainPrivacyGroupContainsMember( |
||||
final OnchainPrivacyGroupContract contract, |
||||
final String privacyGroupId, |
||||
final String privacyUserId, |
||||
final Optional<Long> blockNumber) { |
||||
final Optional<PrivacyGroup> maybePrivacyGroup = |
||||
contract.getPrivacyGroupByIdAndBlockNumber(privacyGroupId, blockNumber); |
||||
// IF the group exists, check member
|
||||
// ELSE member is valid if the group doesn't exist yet - this is normal for onchain privacy
|
||||
// groups
|
||||
maybePrivacyGroup.ifPresent( |
||||
(group) -> { |
||||
if (!group.getMembers().contains(privacyUserId)) { |
||||
throw new MultiTenancyValidationException( |
||||
"Privacy group must contain the enclave public key"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public PrivateTransactionSimulator getTransactionSimulator() { |
||||
return privacyController.getTransactionSimulator(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getStateRootByBlockNumber( |
||||
final String privacyGroupId, final String privacyUserId, final long blockNumber) { |
||||
verifyPrivacyGroupContainsPrivacyUserId( |
||||
privacyGroupId, privacyUserId, Optional.of(blockNumber)); |
||||
return privacyController.getStateRootByBlockNumber(privacyGroupId, privacyUserId, blockNumber); |
||||
} |
||||
} |
@ -0,0 +1,408 @@ |
||||
/* |
||||
* 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 java.util.Collections.emptyList; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE; |
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.PRIVATE_NONCE_TOO_LOW; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.anyBoolean; |
||||
import static org.mockito.ArgumentMatchers.anyList; |
||||
import static org.mockito.ArgumentMatchers.anyString; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.crypto.KeyPair; |
||||
import org.hyperledger.besu.crypto.SignatureAlgorithm; |
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.enclave.Enclave; |
||||
import org.hyperledger.besu.enclave.EnclaveServerException; |
||||
import org.hyperledger.besu.enclave.types.PrivacyGroup; |
||||
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.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.PrivacyParameters; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
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.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
import org.hyperledger.besu.plugin.data.Restriction; |
||||
import org.hyperledger.enclave.testutil.EnclaveKeyUtils; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import com.google.common.base.Supplier; |
||||
import com.google.common.base.Suppliers; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.apache.tuweni.io.Base64; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.util.Lists; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class OnchainPrivacyControllerTest { |
||||
|
||||
private static final String ADDRESS1 = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; |
||||
private static final String ADDRESS2 = "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="; |
||||
private static final String PRIVACY_GROUP_ID = "DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="; |
||||
private static final String KEY = "C2bVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; |
||||
private static final String HEX_STRING_32BYTES_VALUE1 = |
||||
"0x0000000000000000000000000000000000000000000000000000000000000001"; |
||||
|
||||
private OnchainPrivacyController privacyController; |
||||
private PrivateTransactionValidator privateTransactionValidator; |
||||
private PrivateNonceProvider privateNonceProvider; |
||||
private PrivateStateRootResolver privateStateRootResolver; |
||||
private PrivateTransactionSimulator privateTransactionSimulator; |
||||
private PrivateWorldStateReader privateWorldStateReader; |
||||
private Blockchain blockchain; |
||||
private PrivateStateStorage privateStateStorage; |
||||
private Enclave enclave; |
||||
private String enclavePublicKey; |
||||
private OnchainPrivacyController brokenPrivacyController; |
||||
private static final byte[] PAYLOAD = new byte[0]; |
||||
private static final String TRANSACTION_KEY = "93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="; |
||||
|
||||
private static final List<String> PRIVACY_GROUP_ADDRESSES = List.of(ADDRESS1, ADDRESS2); |
||||
private static final Bytes SIMULATOR_RESULT_PREFIX = |
||||
Bytes.fromHexString( |
||||
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002"); |
||||
private static final Bytes SIMULATOR_RESULT = |
||||
Bytes.concatenate(SIMULATOR_RESULT_PREFIX, Base64.decode(ADDRESS1), Base64.decode(ADDRESS2)); |
||||
private static final PrivacyGroup EXPECTED_PRIVACY_GROUP = |
||||
new PrivacyGroup( |
||||
PRIVACY_GROUP_ID, PrivacyGroup.Type.ONCHAIN, "", "", PRIVACY_GROUP_ADDRESSES); |
||||
|
||||
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM = |
||||
Suppliers.memoize(SignatureAlgorithmFactory::getInstance); |
||||
private static final KeyPair KEY_PAIR = |
||||
SIGNATURE_ALGORITHM |
||||
.get() |
||||
.createKeyPair( |
||||
SIGNATURE_ALGORITHM |
||||
.get() |
||||
.createPrivateKey( |
||||
new BigInteger( |
||||
"8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
blockchain = mock(Blockchain.class); |
||||
privateTransactionSimulator = mock(PrivateTransactionSimulator.class); |
||||
privateStateStorage = mock(PrivateStateStorage.class); |
||||
privateNonceProvider = mock(ChainHeadPrivateNonceProvider.class); |
||||
privateStateRootResolver = mock(PrivateStateRootResolver.class); |
||||
|
||||
privateWorldStateReader = mock(PrivateWorldStateReader.class); |
||||
|
||||
privateTransactionValidator = mockPrivateTransactionValidator(); |
||||
enclave = mockEnclave(); |
||||
|
||||
enclavePublicKey = EnclaveKeyUtils.loadKey("enclave_key_0.pub"); |
||||
|
||||
privacyController = |
||||
new OnchainPrivacyController( |
||||
blockchain, |
||||
privateStateStorage, |
||||
enclave, |
||||
privateTransactionValidator, |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privateStateRootResolver); |
||||
brokenPrivacyController = |
||||
new OnchainPrivacyController( |
||||
blockchain, |
||||
privateStateStorage, |
||||
brokenMockEnclave(), |
||||
privateTransactionValidator, |
||||
privateTransactionSimulator, |
||||
privateNonceProvider, |
||||
privateWorldStateReader, |
||||
privateStateRootResolver); |
||||
} |
||||
|
||||
@Test |
||||
public void createsPayload() { |
||||
final PrivateTransaction privateTransaction = buildPrivateTransaction(1).signAndBuild(KEY_PAIR); |
||||
final SendResponse key = new SendResponse(KEY); |
||||
when(enclave.send(any(), any(), anyList())).thenReturn(key); |
||||
final String payload = |
||||
privacyController.createPrivateMarkerTransactionPayload( |
||||
privateTransaction, ADDRESS1, Optional.of(EXPECTED_PRIVACY_GROUP)); |
||||
assertThat(payload).isNotNull().isEqualTo(KEY); |
||||
} |
||||
|
||||
@Test |
||||
public void createsPayloadForAdding() { |
||||
mockingForCreatesPayloadForAdding(); |
||||
final PrivateTransaction privateTransaction = |
||||
buildPrivateTransaction(1) |
||||
.payload(OnchainGroupManagement.ADD_PARTICIPANTS_METHOD_SIGNATURE) |
||||
.to(PrivacyParameters.ONCHAIN_PRIVACY_PROXY) |
||||
.signAndBuild(KEY_PAIR); |
||||
final String payload = |
||||
privacyController.createPrivateMarkerTransactionPayload( |
||||
privateTransaction, ADDRESS1, Optional.of(EXPECTED_PRIVACY_GROUP)); |
||||
assertThat(payload) |
||||
.isNotNull() |
||||
.isEqualTo( |
||||
Bytes.concatenate(Bytes.fromBase64String(KEY), Bytes.fromBase64String(KEY)) |
||||
.toBase64String()); |
||||
} |
||||
|
||||
@Test |
||||
public void verifiesGroupContainsUserId() { |
||||
final OnchainPrivacyGroupContract onchainPrivacyGroupContract = |
||||
mock(OnchainPrivacyGroupContract.class); |
||||
when(onchainPrivacyGroupContract.getPrivacyGroupByIdAndBlockNumber(any(), any())) |
||||
.thenReturn(Optional.of(EXPECTED_PRIVACY_GROUP)); |
||||
privacyController.setOnchainPrivacyGroupContract(onchainPrivacyGroupContract); |
||||
privacyController.verifyPrivacyGroupContainsPrivacyUserId(PRIVACY_GROUP_ID, ADDRESS1); |
||||
} |
||||
|
||||
@Test |
||||
public void findOnchainPrivacyGroups() { |
||||
mockingForFindPrivacyGroupByMembers(); |
||||
mockingForFindPrivacyGroupById(); |
||||
|
||||
final List<PrivacyGroup> privacyGroups = |
||||
List.of(privacyController.findPrivacyGroupByMembers(PRIVACY_GROUP_ADDRESSES, ADDRESS1)); |
||||
assertThat(privacyGroups).hasSize(1); |
||||
assertThat(privacyGroups.get(0)).isEqualToComparingFieldByField(EXPECTED_PRIVACY_GROUP); |
||||
verify(privateStateStorage).getPrivacyGroupHeadBlockMap(any()); |
||||
verify(privateTransactionSimulator).process(any(), any()); |
||||
} |
||||
|
||||
@Test |
||||
public void createsPrivacyGroup() { |
||||
Assertions.assertThatThrownBy( |
||||
() -> privacyController.createPrivacyGroup(Lists.emptyList(), "", "", ADDRESS1)) |
||||
.isInstanceOf(PrivacyConfigurationNotSupportedException.class) |
||||
.hasMessageContaining("Method not supported when using onchain privacy"); |
||||
} |
||||
|
||||
@Test |
||||
public void deletesPrivacyGroup() { |
||||
Assertions.assertThatThrownBy( |
||||
() -> privacyController.deletePrivacyGroup(PRIVACY_GROUP_ID, ADDRESS1)) |
||||
.isInstanceOf(PrivacyConfigurationNotSupportedException.class) |
||||
.hasMessageContaining("Method not supported when using onchain privacy"); |
||||
} |
||||
|
||||
@Test |
||||
public void sendTransactionWhenEnclaveFailsThrowsEnclaveError() { |
||||
final TransactionProcessingResult transactionProcessingResult = |
||||
new TransactionProcessingResult( |
||||
TransactionProcessingResult.Status.SUCCESSFUL, |
||||
Lists.emptyList(), |
||||
0, |
||||
0, |
||||
Bytes32.ZERO, |
||||
ValidationResult.valid(), |
||||
Optional.empty()); |
||||
when(privateTransactionSimulator.process(any(), any())) |
||||
.thenReturn(Optional.of(transactionProcessingResult)); |
||||
Assertions.setMaxStackTraceElementsDisplayed(500); |
||||
assertThatExceptionOfType(EnclaveServerException.class) |
||||
.isThrownBy( |
||||
() -> |
||||
brokenPrivacyController.createPrivateMarkerTransactionPayload( |
||||
buildPrivateTransaction(0).signAndBuild(KEY_PAIR), |
||||
ADDRESS1, |
||||
Optional.of(EXPECTED_PRIVACY_GROUP))); |
||||
} |
||||
|
||||
@Test |
||||
public void validateTransactionWithTooLowNonceReturnsError() { |
||||
when(privateTransactionValidator.validate(any(), any(), anyBoolean())) |
||||
.thenReturn(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); |
||||
|
||||
final PrivateTransaction transaction = buildPrivateTransaction(0).build(); |
||||
final ValidationResult<TransactionInvalidReason> validationResult = |
||||
privacyController.validatePrivateTransaction(transaction, ADDRESS1); |
||||
assertThat(validationResult).isEqualTo(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW)); |
||||
} |
||||
|
||||
@Test |
||||
public void validateTransactionWithIncorrectNonceReturnsError() { |
||||
when(privateTransactionValidator.validate(any(), any(), anyBoolean())) |
||||
.thenReturn(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); |
||||
|
||||
final PrivateTransaction transaction = buildPrivateTransaction(2).build(); |
||||
|
||||
final ValidationResult<TransactionInvalidReason> validationResult = |
||||
privacyController.validatePrivateTransaction(transaction, ADDRESS1); |
||||
assertThat(validationResult).isEqualTo(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE)); |
||||
} |
||||
|
||||
@Test |
||||
public void retrievesTransaction() { |
||||
when(enclave.receive(anyString(), anyString())) |
||||
.thenReturn(new ReceiveResponse(PAYLOAD, PRIVACY_GROUP_ID, null)); |
||||
|
||||
final ReceiveResponse receiveResponse = |
||||
privacyController.retrieveTransaction(TRANSACTION_KEY, ADDRESS1); |
||||
|
||||
assertThat(receiveResponse.getPayload()).isEqualTo(PAYLOAD); |
||||
assertThat(receiveResponse.getPrivacyGroupId()).isEqualTo(PRIVACY_GROUP_ID); |
||||
verify(enclave).receive(TRANSACTION_KEY, enclavePublicKey); |
||||
} |
||||
|
||||
@Test |
||||
public void findsPrivacyGroupById() { |
||||
final PrivacyGroup privacyGroup = |
||||
new PrivacyGroup( |
||||
PRIVACY_GROUP_ID, PrivacyGroup.Type.ONCHAIN, "", "", PRIVACY_GROUP_ADDRESSES); |
||||
mockingForFindPrivacyGroupById(); |
||||
|
||||
final Optional<PrivacyGroup> privacyGroupFound = |
||||
privacyController.findPrivacyGroupByGroupId(PRIVACY_GROUP_ID, ADDRESS1); |
||||
assertThat(privacyGroupFound).isPresent(); |
||||
assertThat(privacyGroupFound.get()).isEqualToComparingFieldByField(privacyGroup); |
||||
} |
||||
|
||||
@Test |
||||
public void findsPrivacyGroupByIdEmpty() { |
||||
when(privateTransactionSimulator.process(any(), any())).thenReturn(Optional.empty()); |
||||
|
||||
final Optional<PrivacyGroup> privacyGroupFound = |
||||
privacyController.findPrivacyGroupByGroupId(PRIVACY_GROUP_ID, ADDRESS1); |
||||
assertThat(privacyGroupFound).isEmpty(); |
||||
} |
||||
|
||||
private Enclave brokenMockEnclave() { |
||||
final Enclave mockEnclave = mock(Enclave.class); |
||||
when(mockEnclave.send(anyString(), anyString(), anyList())) |
||||
.thenThrow(EnclaveServerException.class); |
||||
return mockEnclave; |
||||
} |
||||
|
||||
private static PrivateTransaction.Builder buildPrivateTransaction(final long nonce) { |
||||
return PrivateTransaction.builder() |
||||
.nonce(nonce) |
||||
.gasPrice(Wei.of(1000)) |
||||
.gasLimit(3000000) |
||||
.to(Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57")) |
||||
.value(Wei.ZERO) |
||||
.payload(Bytes.fromHexString("0x00")) |
||||
.sender(Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73")) |
||||
.chainId(BigInteger.valueOf(1337)) |
||||
.restriction(Restriction.RESTRICTED) |
||||
.privateFrom(Base64.decode(ADDRESS1)) |
||||
.privacyGroupId(Base64.decode(PRIVACY_GROUP_ID)); |
||||
} |
||||
|
||||
private Enclave mockEnclave() { |
||||
final Enclave mockEnclave = mock(Enclave.class); |
||||
return mockEnclave; |
||||
} |
||||
|
||||
private PrivateTransactionValidator mockPrivateTransactionValidator() { |
||||
final PrivateTransactionValidator validator = mock(PrivateTransactionValidator.class); |
||||
return validator; |
||||
} |
||||
|
||||
private void mockingForFindPrivacyGroupByMembers() { |
||||
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = |
||||
new PrivacyGroupHeadBlockMap( |
||||
Map.of(Bytes32.wrap(Bytes.fromBase64String(PRIVACY_GROUP_ID)), Hash.ZERO)); |
||||
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any())) |
||||
.thenReturn(Optional.of(privacyGroupHeadBlockMap)); |
||||
} |
||||
|
||||
private void mockingForFindPrivacyGroupById() { |
||||
when(privateTransactionSimulator.process(any(), any())) |
||||
.thenReturn( |
||||
Optional.of( |
||||
new TransactionProcessingResult( |
||||
TransactionProcessingResult.Status.SUCCESSFUL, |
||||
emptyList(), |
||||
0, |
||||
0, |
||||
SIMULATOR_RESULT, |
||||
ValidationResult.valid(), |
||||
Optional.empty()))); |
||||
} |
||||
|
||||
private void mockingForCreatesPayloadForAdding() { |
||||
final SendResponse key = new SendResponse(KEY); |
||||
when(enclave.send(any(), any(), anyList())).thenReturn(key); |
||||
final Map<Bytes32, Hash> bytes32HashMap = new HashMap<>(); |
||||
final Bytes32 pgBytes = Bytes32.wrap(Base64.decode(PRIVACY_GROUP_ID)); |
||||
bytes32HashMap.put(pgBytes, Hash.ZERO); |
||||
when(blockchain.getChainHeadHash()).thenReturn(Hash.ZERO); |
||||
final Optional<PrivacyGroupHeadBlockMap> privacyGroupHeadBlockMap = |
||||
Optional.of(new PrivacyGroupHeadBlockMap(bytes32HashMap)); |
||||
when(privateStateStorage.getPrivacyGroupHeadBlockMap(Hash.ZERO)) |
||||
.thenReturn(privacyGroupHeadBlockMap); |
||||
final List<PrivateTransactionMetadata> privateTransactionMetadata = |
||||
List.of(new PrivateTransactionMetadata(Hash.ZERO, Hash.ZERO)); |
||||
final PrivateBlockMetadata privateBlockMetadata = |
||||
new PrivateBlockMetadata(privateTransactionMetadata); |
||||
when(privateStateStorage.getPrivateBlockMetadata(any(), eq(pgBytes))) |
||||
.thenReturn(Optional.of(privateBlockMetadata)); |
||||
final BlockHeader blockHeaderMock = mock(BlockHeader.class); |
||||
final Optional<BlockHeader> maybeBlockHeader = Optional.of(blockHeaderMock); |
||||
when(blockchain.getBlockHeader(any())).thenReturn(maybeBlockHeader); |
||||
final Hash hash0x01 = Hash.fromHexString(HEX_STRING_32BYTES_VALUE1); |
||||
when(blockHeaderMock.getParentHash()).thenReturn(hash0x01); |
||||
when(privateStateStorage.getPrivacyGroupHeadBlockMap(hash0x01)).thenReturn(Optional.empty()); |
||||
final Transaction transaction = |
||||
Transaction.builder() |
||||
.gasLimit(0) |
||||
.gasPrice(Wei.ZERO) |
||||
.nonce(0) |
||||
.payload(Bytes.fromHexString(HEX_STRING_32BYTES_VALUE1)) |
||||
.value(Wei.ZERO) |
||||
.to(null) |
||||
.guessType() |
||||
.signAndBuild(KEY_PAIR); |
||||
when(blockchain.getTransactionByHash(any())).thenReturn(Optional.ofNullable(transaction)); |
||||
final PrivateTransactionWithMetadata privateTransactionWithMetadata = |
||||
new PrivateTransactionWithMetadata( |
||||
buildPrivateTransaction(3).signAndBuild(KEY_PAIR), |
||||
new PrivateTransactionMetadata(Hash.ZERO, Hash.ZERO)); |
||||
final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); |
||||
privateTransactionWithMetadata.writeTo(bytesValueRLPOutput); |
||||
final byte[] txPayload = |
||||
bytesValueRLPOutput.encoded().toBase64String().getBytes(StandardCharsets.UTF_8); |
||||
when(enclave.receive(any(), any())) |
||||
.thenReturn(new ReceiveResponse(txPayload, PRIVACY_GROUP_ID, ADDRESS2)); |
||||
} |
||||
} |
Loading…
Reference in new issue