Skip to content

Commit

Permalink
eth_call now supports GoQuorum private transactions (#1934)
Browse files Browse the repository at this point in the history
eth_call now supports GoQuorum private transactions

Signed-off-by: Sally MacFarlane <[email protected]>
  • Loading branch information
macfarla authored Mar 2, 2021
1 parent f25d458 commit 711bbfb
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
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;
Expand All @@ -83,6 +84,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
private final TransactionPool transactionPool;
private final MiningCoordinator miningCoordinator;
private final Set<Capability> supportedCapabilities;
private final PrivacyParameters privacyParameters;

public EthJsonRpcMethods(
final BlockchainQueries blockchainQueries,
Expand All @@ -91,14 +93,16 @@ public EthJsonRpcMethods(
final FilterManager filterManager,
final TransactionPool transactionPool,
final MiningCoordinator miningCoordinator,
final Set<Capability> supportedCapabilities) {
final Set<Capability> supportedCapabilities,
final PrivacyParameters privacyParameters) {
this.blockchainQueries = blockchainQueries;
this.synchronizer = synchronizer;
this.protocolSchedule = protocolSchedule;
this.filterManager = filterManager;
this.transactionPool = transactionPool;
this.miningCoordinator = miningCoordinator;
this.supportedCapabilities = supportedCapabilities;
this.privacyParameters = privacyParameters;
}

@Override
Expand All @@ -121,7 +125,8 @@ protected Map<String, JsonRpcMethod> create() {
new TransactionSimulator(
blockchainQueries.getBlockchain(),
blockchainQueries.getWorldStateArchive(),
protocolSchedule)),
protocolSchedule,
privacyParameters)),
new EthGetCode(blockchainQueries),
new EthGetLogs(blockchainQueries),
new EthGetProof(blockchainQueries),
Expand Down Expand Up @@ -149,7 +154,8 @@ protected Map<String, JsonRpcMethod> create() {
new TransactionSimulator(
blockchainQueries.getBlockchain(),
blockchainQueries.getWorldStateArchive(),
protocolSchedule)),
protocolSchedule,
privacyParameters)),
new EthMining(miningCoordinator),
new EthCoinbase(miningCoordinator),
new EthProtocolVersion(supportedCapabilities),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ public Map<String, JsonRpcMethod> methods(
filterManager,
transactionPool,
miningCoordinator,
supportedCapabilities),
supportedCapabilities,
privacyParameters),
new NetJsonRpcMethods(
p2pNetwork,
networkId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,12 +30,8 @@

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;

Expand Down Expand Up @@ -66,34 +61,9 @@ public GoQuorumBlockValidator(
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<MutableWorldState> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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> 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<MutableWorldState> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +25,7 @@
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;
Expand All @@ -33,6 +36,7 @@
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;

Expand All @@ -50,7 +54,6 @@
* blockchain or to estimate the transaction gas cost.
*/
public class TransactionSimulator {

private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);

Expand All @@ -71,6 +74,7 @@ public class TransactionSimulator {
private final Blockchain blockchain;
private final WorldStateArchive worldStateArchive;
private final ProtocolSchedule protocolSchedule;
private final Optional<PrivacyParameters> maybePrivacyParameters;

public TransactionSimulator(
final Blockchain blockchain,
Expand All @@ -79,6 +83,18 @@ public 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<TransactionSimulatorResult> process(
Expand Down Expand Up @@ -125,24 +141,25 @@ public Optional<TransactionSimulatorResult> process(
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();
final Wei gasPrice = callParams.getGasPrice() != null ? callParams.getGasPrice() : Wei.ZERO;
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));
}
Expand Down Expand Up @@ -182,17 +199,34 @@ public Optional<TransactionSimulatorResult> process(
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<Boolean> 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<Boolean> doesAddressExist(final Address address, final BlockHeader header) {
public Optional<Boolean> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 711bbfb

Please sign in to comment.