From 711bbfbd5e2ac175102260a16ba254beadc47daf Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 2 Mar 2021 12:21:28 +1000 Subject: [PATCH] eth_call now supports GoQuorum private transactions (#1934) eth_call now supports GoQuorum private transactions Signed-off-by: Sally MacFarlane --- .../jsonrpc/methods/EthJsonRpcMethods.java | 12 +++- .../methods/JsonRpcMethodsFactory.java | 3 +- .../goquorum/GoQuorumBlockValidator.java | 34 +-------- .../goquorum/GoQuorumPrivateStateUtil.java | 71 +++++++++++++++++++ .../transaction/TransactionSimulator.java | 56 ++++++++++++--- ...efaultMutablePrivateWorldStateUpdater.java | 2 +- ...ablePrivateAndPublicWorldStateUpdater.java | 44 ++++++++++++ 7 files changed, 174 insertions(+), 48 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumPrivateStateUtil.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/GoQuorumMutablePrivateAndPublicWorldStateUpdater.java diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java index 1a2d13be1e..839fd37eb9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java @@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -83,6 +84,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods { private final TransactionPool transactionPool; private final MiningCoordinator miningCoordinator; private final Set supportedCapabilities; + private final PrivacyParameters privacyParameters; public EthJsonRpcMethods( final BlockchainQueries blockchainQueries, @@ -91,7 +93,8 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods { final FilterManager filterManager, final TransactionPool transactionPool, final MiningCoordinator miningCoordinator, - final Set supportedCapabilities) { + final Set supportedCapabilities, + final PrivacyParameters privacyParameters) { this.blockchainQueries = blockchainQueries; this.synchronizer = synchronizer; this.protocolSchedule = protocolSchedule; @@ -99,6 +102,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods { this.transactionPool = transactionPool; this.miningCoordinator = miningCoordinator; this.supportedCapabilities = supportedCapabilities; + this.privacyParameters = privacyParameters; } @Override @@ -121,7 +125,8 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods { new TransactionSimulator( blockchainQueries.getBlockchain(), blockchainQueries.getWorldStateArchive(), - protocolSchedule)), + protocolSchedule, + privacyParameters)), new EthGetCode(blockchainQueries), new EthGetLogs(blockchainQueries), new EthGetProof(blockchainQueries), @@ -149,7 +154,8 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods { new TransactionSimulator( blockchainQueries.getBlockchain(), blockchainQueries.getWorldStateArchive(), - protocolSchedule)), + protocolSchedule, + privacyParameters)), new EthMining(miningCoordinator), new EthCoinbase(miningCoordinator), new EthProtocolVersion(supportedCapabilities), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index d8f42a5fef..05694a3881 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -102,7 +102,8 @@ public class JsonRpcMethodsFactory { filterManager, transactionPool, miningCoordinator, - supportedCapabilities), + supportedCapabilities, + privacyParameters), new NetJsonRpcMethods( p2pNetwork, networkId, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockValidator.java index 5f1c363e9a..0d39807458 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockValidator.java @@ -14,14 +14,13 @@ */ package org.hyperledger.besu.ethereum.goquorum; -import static org.apache.logging.log4j.LogManager.getLogger; +import static org.hyperledger.besu.ethereum.goquorum.GoQuorumPrivateStateUtil.getPrivateWorldState; import org.hyperledger.besu.ethereum.MainnetBlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.GoQuorumPrivacyParameters; -import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.mainnet.BlockBodyValidator; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; @@ -31,12 +30,8 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Optional; -import org.apache.logging.log4j.Logger; - public class GoQuorumBlockValidator extends MainnetBlockValidator { - private static final Logger LOG = getLogger(); - private final GoQuorumPrivateStorage goQuorumPrivateStorage; private final WorldStateArchive goQuorumWorldStateArchive; @@ -66,34 +61,9 @@ public class GoQuorumBlockValidator extends MainnetBlockValidator { protected Result processBlock( final ProtocolContext context, final MutableWorldState worldState, final Block block) { final MutableWorldState privateWorldState = - getPrivateWorldState(worldState.rootHash(), block.getHash()); + getPrivateWorldState(goQuorumPrivateStorage, goQuorumWorldStateArchive, block.getHeader()); return ((GoQuorumBlockProcessor) blockProcessor) .processBlock(context.getBlockchain(), worldState, privateWorldState, block); } - - private MutableWorldState getPrivateWorldState( - final Hash worldStateRootHash, final Hash publicBlockHash) { - final Hash privateStateRootHash = - goQuorumPrivateStorage - .getPrivateStateRootHash(worldStateRootHash) - .orElse(Hash.EMPTY_TRIE_HASH); - - final Optional maybePrivateWorldState = - goQuorumWorldStateArchive.getMutable(privateStateRootHash, publicBlockHash); - if (maybePrivateWorldState.isEmpty()) { - LOG.debug( - "Private world state not available for public world state root hash {}, public block hash {}", - worldStateRootHash, - publicBlockHash); - - /* - This should never happen because privateStateRootResolver will either return a matching - private world state root hash, or the hash for an empty world state (first private tx ever). - */ - throw new IllegalStateException( - "Private world state not available for public world state root hash " + publicBlockHash); - } - return maybePrivateWorldState.get(); - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumPrivateStateUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumPrivateStateUtil.java new file mode 100644 index 0000000000..5d7b32a59d --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumPrivateStateUtil.java @@ -0,0 +1,71 @@ +/* + * 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.goquorum; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.GoQuorumPrivacyParameters; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.util.Optional; + +import org.apache.logging.log4j.Logger; + +public class GoQuorumPrivateStateUtil { + private static final Logger LOG = getLogger(); + + public static MutableWorldState getPrivateWorldState( + final Optional goQuorumPrivacyParameters, + final BlockHeader header) { + final GoQuorumPrivateStorage goQuorumPrivateStorage = + goQuorumPrivacyParameters.orElseThrow().privateStorage(); + final WorldStateArchive goQuorumWorldStateArchive = + goQuorumPrivacyParameters.orElseThrow().worldStateArchive(); + return getPrivateWorldState(goQuorumPrivateStorage, goQuorumWorldStateArchive, header); + } + + public static MutableWorldState getPrivateWorldState( + final GoQuorumPrivateStorage goQuorumPrivateStorage, + final WorldStateArchive goQuorumWorldStateArchive, + final BlockHeader header) { + final Hash worldStateRootHash = header.getStateRoot(); + final Hash publicBlockHash = header.getHash(); + final Hash privateStateRootHash = + goQuorumPrivateStorage + .getPrivateStateRootHash(worldStateRootHash) + .orElse(Hash.EMPTY_TRIE_HASH); + + final Optional maybePrivateWorldState = + goQuorumWorldStateArchive.getMutable(privateStateRootHash, publicBlockHash); + if (maybePrivateWorldState.isEmpty()) { + LOG.debug( + "Private world state not available for public world state root hash {}, public block hash {}", + worldStateRootHash, + publicBlockHash); + + /* + This should never happen because privateStateRootResolver will either return a matching + private world state root hash, or the hash for an empty world state (first private tx ever). + */ + throw new IllegalStateException( + "Private world state not available for public world state root hash " + publicBlockHash); + } + return maybePrivateWorldState.get(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 04a5631a45..e3ed75bb9f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.transaction; +import static org.hyperledger.besu.ethereum.goquorum.GoQuorumPrivateStateUtil.getPrivateWorldState; + import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -23,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.Address; 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.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.core.WorldUpdater; @@ -33,6 +36,7 @@ import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.OperationTracer; +import org.hyperledger.besu.ethereum.worldstate.GoQuorumMutablePrivateAndPublicWorldStateUpdater; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.data.TransactionType; @@ -50,7 +54,6 @@ import org.apache.tuweni.units.bigints.UInt256; * blockchain or to estimate the transaction gas cost. */ public class TransactionSimulator { - private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -71,6 +74,7 @@ public class TransactionSimulator { private final Blockchain blockchain; private final WorldStateArchive worldStateArchive; private final ProtocolSchedule protocolSchedule; + private final Optional maybePrivacyParameters; public TransactionSimulator( final Blockchain blockchain, @@ -79,6 +83,18 @@ public class TransactionSimulator { this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; this.protocolSchedule = protocolSchedule; + this.maybePrivacyParameters = Optional.empty(); + } + + public TransactionSimulator( + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final ProtocolSchedule protocolSchedule, + final PrivacyParameters privacyParameters) { + this.blockchain = blockchain; + this.worldStateArchive = worldStateArchive; + this.protocolSchedule = protocolSchedule; + this.maybePrivacyParameters = Optional.of(privacyParameters); } public Optional process( @@ -125,15 +141,18 @@ public class TransactionSimulator { if (header == null) { return Optional.empty(); } - final MutableWorldState worldState = + + final MutableWorldState publicWorldState = worldStateArchive.getMutable(header.getStateRoot(), header.getHash()).orElse(null); - if (worldState == null) { + + if (publicWorldState == null) { return Optional.empty(); } + final WorldUpdater updater = getEffectiveWorldStateUpdater(header, publicWorldState); final Address senderAddress = callParams.getFrom() != null ? callParams.getFrom() : DEFAULT_FROM; - final Account sender = worldState.get(senderAddress); + final Account sender = publicWorldState.get(senderAddress); final long nonce = sender != null ? sender.getNonce() : 0L; final long gasLimit = callParams.getGasLimit() >= 0 ? callParams.getGasLimit() : header.getGasLimit(); @@ -141,8 +160,6 @@ public class TransactionSimulator { final Wei value = callParams.getValue() != null ? callParams.getValue() : Wei.ZERO; final Bytes payload = callParams.getPayload() != null ? callParams.getPayload() : Bytes.EMPTY; - final WorldUpdater updater = worldState.updater(); - if (transactionValidationParams.isAllowExceedingBalance()) { updater.getOrCreate(senderAddress).getMutable().setBalance(Wei.of(UInt256.MAX_VALUE)); } @@ -182,17 +199,34 @@ public class TransactionSimulator { return Optional.of(new TransactionSimulatorResult(transaction, result)); } + // return combined private/public world state updater if GoQuorum mode, otherwise the public state + private WorldUpdater getEffectiveWorldStateUpdater( + final BlockHeader header, final MutableWorldState publicWorldState) { + + if (maybePrivacyParameters.isPresent() + && maybePrivacyParameters.get().getGoQuorumPrivacyParameters().isPresent()) { + + final MutableWorldState privateWorldState = + getPrivateWorldState(maybePrivacyParameters.get().getGoQuorumPrivacyParameters(), header); + return new GoQuorumMutablePrivateAndPublicWorldStateUpdater( + publicWorldState.updater(), privateWorldState.updater()); + } + return publicWorldState.updater(); + } + public Optional doesAddressExistAtHead(final Address address) { - return doesAddressExist(address, blockchain.getChainHeadHeader()); + final BlockHeader header = blockchain.getChainHeadHeader(); + final MutableWorldState worldState = + worldStateArchive.getMutable(header.getStateRoot(), header.getHash()).orElse(null); + + return doesAddressExist(worldState, address, header); } - public Optional doesAddressExist(final Address address, final BlockHeader header) { + public Optional doesAddressExist( + final MutableWorldState worldState, final Address address, final BlockHeader header) { if (header == null) { return Optional.empty(); } - - final MutableWorldState worldState = - worldStateArchive.getMutable(header.getStateRoot(), header.getHash()).orElse(null); if (worldState == null) { return Optional.empty(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java index f3a078576d..bc84f0b012 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java @@ -30,7 +30,7 @@ import java.util.Optional; public class DefaultMutablePrivateWorldStateUpdater implements WorldUpdater { protected final WorldUpdater publicWorldUpdater; - private final WorldUpdater privateWorldUpdater; + protected final WorldUpdater privateWorldUpdater; public DefaultMutablePrivateWorldStateUpdater( final WorldUpdater publicWorldUpdater, final WorldUpdater privateWorldUpdater) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/GoQuorumMutablePrivateAndPublicWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/GoQuorumMutablePrivateAndPublicWorldStateUpdater.java new file mode 100644 index 0000000000..022c8406f1 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/GoQuorumMutablePrivateAndPublicWorldStateUpdater.java @@ -0,0 +1,44 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.worldstate; + +import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.EvmAccount; +import org.hyperledger.besu.ethereum.core.WorldUpdater; + +// This class uses a public WorldUpdater and a private WorldUpdater to provide a +// MutableWorldStateUpdater that can read and write from BOTH the private world state and the public +// world state. +public class GoQuorumMutablePrivateAndPublicWorldStateUpdater + extends GoQuorumMutablePrivateWorldStateUpdater { + + public GoQuorumMutablePrivateAndPublicWorldStateUpdater( + final WorldUpdater publicWorldUpdater, final WorldUpdater privateWorldUpdater) { + super(publicWorldUpdater, privateWorldUpdater); + } + + @Override + public EvmAccount getAccount(final Address address) { + final EvmAccount privateAccount = privateWorldUpdater.getAccount(address); + if (privateAccount != null && !privateAccount.isEmpty()) { + return privateAccount; + } + final EvmAccount publicAccount = publicWorldUpdater.getAccount(address); + if (publicAccount != null && !publicAccount.isEmpty()) { + return publicAccount; + } + return privateAccount; + } +}