Fix #1110: Privacy: Restart caused by insufficient memory can cause inconsistent private state (#1206)

* Fix #1110: Privacy: Restart caused by insufficient memory can cause inconsistent private state

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>
pull/1354/head
Stefan Pingel 4 years ago committed by GitHub
parent 473ac77275
commit eb3941976c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  2. 9
      ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java
  3. 9
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java
  4. 27
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java
  5. 7
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java
  6. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  7. 26
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java
  8. 56
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionProcessor.java
  9. 43
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContract.java
  10. 113
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java
  11. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/DefaultPrivacyController.java
  12. 97
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java
  13. 35
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java
  14. 31
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRootResolver.java
  15. 5
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivacyGroupHeadBlockMap.java
  16. 83
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateMetadataUpdater.java
  17. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigration.java
  18. 15
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/MessageFrame.java
  19. 34
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java
  20. 14
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/OnChainPrivacyPrecompiledContractTest.java
  21. 33
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java
  22. 87
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateMetadataUpdaterTest.java
  23. 25
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigrationTest.java

@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.InMemoryPrivacyStorageProvider;
import org.hyperledger.besu.ethereum.core.InMemoryStorageProvider;
import org.hyperledger.besu.ethereum.core.LogsBloomFilter;
import org.hyperledger.besu.ethereum.core.MiningParametersTestBuilder;
@ -53,15 +54,8 @@ import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory;
import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory;
import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory;
import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration;
import org.hyperledger.besu.services.BesuConfigurationImpl;
import org.hyperledger.besu.testutil.TestClock;
import org.hyperledger.orion.testutil.OrionKeyConfiguration;
import org.hyperledger.orion.testutil.OrionTestHarness;
@ -71,7 +65,6 @@ import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -87,11 +80,6 @@ import org.junit.rules.TemporaryFolder;
@SuppressWarnings("rawtypes")
public class PrivacyReorgTest {
private static final int MAX_OPEN_FILES = 1024;
private static final long CACHE_CAPACITY = 8388608;
private static final int MAX_BACKGROUND_COMPACTIONS = 4;
private static final int BACKGROUND_THREAD_COUNT = 4;
@Rule public final TemporaryFolder folder = new TemporaryFolder();
private static final SECP256K1.KeyPair KEY_PAIR =
@ -149,13 +137,12 @@ public class PrivacyReorgTest {
// Create Storage
final Path dataDir = folder.newFolder().toPath();
final Path dbDir = dataDir.resolve("database");
// Configure Privacy
privacyParameters =
new PrivacyParameters.Builder()
.setEnabled(true)
.setStorageProvider(createKeyValueStorageProvider(dataDir, dbDir))
.setStorageProvider(createKeyValueStorageProvider())
.setEnclaveUrl(enclave.clientUrl())
.setEnclaveFactory(new EnclaveFactory(Vertx.vertx()))
.build();
@ -396,23 +383,8 @@ public class PrivacyReorgTest {
.importBlock(protocolContext, block, HeaderValidationMode.NONE);
}
private PrivacyStorageProvider createKeyValueStorageProvider(
final Path dataLocation, final Path dbLocation) {
return new PrivacyKeyValueStorageProviderBuilder()
.withStorageFactory(
new RocksDBKeyValuePrivacyStorageFactory(
new RocksDBKeyValueStorageFactory(
() ->
new RocksDBFactoryConfiguration(
MAX_OPEN_FILES,
MAX_BACKGROUND_COMPACTIONS,
BACKGROUND_THREAD_COUNT,
CACHE_CAPACITY),
Arrays.asList(KeyValueSegmentIdentifier.values()),
RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS)))
.withCommonConfiguration(new BesuConfigurationImpl(dataLocation, dbLocation))
.withMetricsSystem(new NoOpMetricsSystem())
.build();
private PrivacyStorageProvider createKeyValueStorageProvider() {
return new InMemoryPrivacyStorageProvider();
}
private Bytes getEnclaveKey(final URI enclaveURI) {

@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
@ -135,6 +136,11 @@ public class PrivacyPrecompiledContractIntegrationTest {
when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis));
when(messageFrame.getBlockchain()).thenReturn(blockchain);
when(messageFrame.getBlockHeader()).thenReturn(block.getHeader());
final PrivateMetadataUpdater privateMetadataUpdater = mock(PrivateMetadataUpdater.class);
when(privateMetadataUpdater.getPrivateBlockMetadata(any())).thenReturn(null);
when(privateMetadataUpdater.getPrivacyGroupHeadBlockMap())
.thenReturn(PrivacyGroupHeadBlockMap.empty());
when(messageFrame.getPrivateMetadataUpdater()).thenReturn(privateMetadataUpdater);
worldStateArchive = mock(WorldStateArchive.class);
final MutableWorldState mutableWorldState = mock(MutableWorldState.class);
@ -145,7 +151,7 @@ public class PrivacyPrecompiledContractIntegrationTest {
privateStateStorage = mock(PrivateStateStorage.class);
final PrivateStateStorage.Updater storageUpdater = mock(PrivateStateStorage.Updater.class);
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any()))
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY));
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.empty()));
when(storageUpdater.putPrivateBlockMetadata(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
@ -184,7 +190,6 @@ public class PrivacyPrecompiledContractIntegrationTest {
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
new PrivateStateRootResolver(privateStateStorage));
privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor());

@ -25,7 +25,9 @@ import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldState;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.core.fees.TransactionGasBudgetCalculator;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.OperationTracer;
import java.util.ArrayList;
import java.util.List;
@ -113,7 +115,8 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
final List<BlockHeader> ommers,
final PrivateMetadataUpdater privateMetadataUpdater) {
long legacyGasUsed = 0;
long eip1556GasUsed = 0;
@ -148,9 +151,11 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
blockHeader,
transaction,
miningBeneficiary,
OperationTracer.NO_TRACING,
blockHashLookup,
true,
TransactionValidationParams.processingBlock());
TransactionValidationParams.processingBlock(),
privateMetadataUpdater);
if (result.isInvalid()) {
return AbstractBlockProcessor.Result.failed();
}

@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import java.util.List;
@ -62,7 +63,8 @@ public interface BlockProcessor {
worldState,
block.getHeader(),
block.getBody().getTransactions(),
block.getBody().getOmmers());
block.getBody().getOmmers(),
null);
}
/**
@ -75,12 +77,33 @@ public interface BlockProcessor {
* @param ommers the block ommers
* @return the block processing result
*/
default Result processBlock(
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
return processBlock(blockchain, worldState, blockHeader, transactions, ommers, null);
}
/**
* Processes the block.
*
* @param blockchain the blockchain to append the block to
* @param worldState the world state to apply changes to
* @param blockHeader the block header for the block
* @param transactions the transactions in the block
* @param ommers the block ommers
* @param privateMetadataUpdater the updater used to update the private metadata for the block
* @return the block processing result
*/
Result processBlock(
Blockchain blockchain,
MutableWorldState worldState,
BlockHeader blockHeader,
List<Transaction> transactions,
List<BlockHeader> ommers);
List<BlockHeader> ommers,
PrivateMetadataUpdater privateMetadataUpdater);
/**
* Get ommer reward in ${@link Wei}

@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.fees.TransactionPriceCalculator;
import org.hyperledger.besu.ethereum.mainnet.contractvalidation.MaxCodeSizeRule;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.vm.MessageFrame;
import java.io.IOException;
@ -462,9 +463,11 @@ public abstract class MainnetProtocolSpecs {
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
final List<BlockHeader> ommers,
final PrivateMetadataUpdater privateMetadataUpdater) {
updateWorldStateForDao(worldState);
return wrapped.processBlock(blockchain, worldState, blockHeader, transactions, ommers);
return wrapped.processBlock(
blockchain, worldState, blockHeader, transactions, ommers, privateMetadataUpdater);
}
private static final Address DAO_REFUND_CONTRACT_ADDRESS =

@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.core.fees.CoinbaseFeePriceCalculator;
import org.hyperledger.besu.ethereum.core.fees.TransactionPriceCalculator;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.Code;
import org.hyperledger.besu.ethereum.vm.GasCalculator;
@ -208,7 +209,8 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
final OperationTracer operationTracer,
final BlockHashLookup blockHashLookup,
final Boolean isPersistingPrivateState,
final TransactionValidationParams transactionValidationParams) {
final TransactionValidationParams transactionValidationParams,
final PrivateMetadataUpdater privateMetadataUpdater) {
try {
LOG.trace("Starting execution of {}", transaction);
@ -293,6 +295,7 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
.isPersistingPrivateState(isPersistingPrivateState)
.maxStackSize(maxStackSize)
.transactionHash(transaction.getHash())
.privateMetadataUpdater(privateMetadataUpdater)
.build();
} else {
@ -326,6 +329,7 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
.maxStackSize(maxStackSize)
.isPersistingPrivateState(isPersistingPrivateState)
.transactionHash(transaction.getHash())
.privateMetadataUpdater(privateMetadataUpdater)
.build();
}

@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
@ -79,20 +80,23 @@ public class PrivacyBlockProcessor implements BlockProcessor {
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
final List<BlockHeader> ommers,
final PrivateMetadataUpdater privateMetadataUpdater) {
if (privateMetadataUpdater != null) {
throw new IllegalArgumentException("PrivateMetadataUpdater passed in is not null.");
}
maybeRehydrate(blockchain, blockHeader, transactions);
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
new PrivacyGroupHeadBlockMap(
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockHeader.getParentHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY));
privateStateStorage
.updater()
.putPrivacyGroupHeadBlockMap(blockHeader.getHash(), privacyGroupHeadBlockMap)
.commit();
return blockProcessor.processBlock(blockchain, worldState, blockHeader, transactions, ommers);
final PrivateMetadataUpdater metadataUpdater =
new PrivateMetadataUpdater(blockHeader, privateStateStorage);
final Result result =
blockProcessor.processBlock(
blockchain, worldState, blockHeader, transactions, ommers, metadataUpdater);
metadataUpdater.commit();
return result;
}
void maybeRehydrate(

@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.Log;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.OperationTracer;
@ -225,6 +226,58 @@ public interface TransactionProcessor {
new TransactionValidationParams.Builder().build());
}
/**
* Applies a transaction to the current system state.
*
* @param blockchain The current blockchain
* @param worldState The current world state
* @param blockHeader The current block header
* @param transaction The transaction to process
* @param operationTracer The tracer to record results of each EVM operation
* @param miningBeneficiary The address which is to receive the transaction fee
* @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations
* @param isPersistingPrivateState Whether the resulting private state will be persisted
* @param transactionValidationParams The transaction validation parameters to use
* @return the transaction result
*/
default Result processTransaction(
final Blockchain blockchain,
final WorldUpdater worldState,
final ProcessableBlockHeader blockHeader,
final Transaction transaction,
final Address miningBeneficiary,
final OperationTracer operationTracer,
final BlockHashLookup blockHashLookup,
final Boolean isPersistingPrivateState,
final TransactionValidationParams transactionValidationParams) {
return processTransaction(
blockchain,
worldState,
blockHeader,
transaction,
miningBeneficiary,
operationTracer,
blockHashLookup,
isPersistingPrivateState,
transactionValidationParams,
null);
}
/**
* Applies a transaction to the current system state.
*
* @param blockchain The current blockchain
* @param worldState The current world state
* @param blockHeader The current block header
* @param transaction The transaction to process
* @param operationTracer The tracer to record results of each EVM operation
* @param miningBeneficiary The address which is to receive the transaction fee
* @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations
* @param isPersistingPrivateState Whether the resulting private state will be persisted
* @param transactionValidationParams The transaction validation parameters to use
* @param privateMetadataUpdater The updater to use for the private metadata
* @return the transaction result
*/
Result processTransaction(
Blockchain blockchain,
WorldUpdater worldState,
@ -234,5 +287,6 @@ public interface TransactionProcessor {
OperationTracer operationTracer,
BlockHashLookup blockHashLookup,
Boolean isPersistingPrivateState,
TransactionValidationParams transactionValidationParams);
TransactionValidationParams transactionValidationParams,
PrivateMetadataUpdater privateMetadataUpdater);
}

@ -22,7 +22,6 @@ import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.DefaultEvmAccount;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.MutableAccount;
@ -37,10 +36,11 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionEvent;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionObserver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt;
import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
@ -73,23 +73,21 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
private static final Logger LOG = LogManager.getLogger();
public OnChainPrivacyPrecompiledContract(
final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) {
super(gasCalculator, privacyParameters, "OnChainPrivacy");
}
public OnChainPrivacyPrecompiledContract(
final GasCalculator gasCalculator,
final Enclave enclave,
final WorldStateArchive worldStateArchive,
final PrivateStateStorage privateStateStorage,
final PrivateStateRootResolver privateStateRootResolver) {
super(gasCalculator, enclave, worldStateArchive, privateStateRootResolver, "OnChainPrivacy");
}
public OnChainPrivacyPrecompiledContract(
final GasCalculator gasCalculator, final PrivacyParameters privacyParameters) {
super(
gasCalculator,
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver,
privacyParameters.getEnclave(),
privacyParameters.getPrivateWorldStateArchive(),
privacyParameters.getPrivateStateRootResolver(),
"OnChainPrivacy");
}
@ -143,10 +141,10 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
LOG.debug("Processing private transaction {} in privacy group {}", pmtHash, privacyGroupId);
final ProcessableBlockHeader currentBlockHeader = messageFrame.getBlockHeader();
final Hash currentBlockHash = ((BlockHeader) currentBlockHeader).getHash();
final PrivateMetadataUpdater privateMetadataUpdater = messageFrame.getPrivateMetadataUpdater();
final Hash lastRootHash =
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, currentBlockHash);
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, privateMetadataUpdater);
final MutableWorldState disposablePrivateState =
privateWorldStateArchive.getMutable(lastRootHash).get();
@ -183,9 +181,7 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
pmtHash,
result.getValidationResult().getErrorMessage());
final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater();
storeTransactionReceipt(pmtHash, currentBlockHash, result, privateStateUpdater);
privateStateUpdater.commit();
privateMetadataUpdater.putTransactionReceipt(pmtHash, new PrivateTransactionReceipt(result));
return Bytes.EMPTY;
}
@ -193,13 +189,12 @@ public class OnChainPrivacyPrecompiledContract extends PrivacyPrecompiledContrac
sendParticipantRemovedEvent(privateTransaction);
if (messageFrame.isPersistingPrivateState()) {
persistPrivateState(
pmtHash,
currentBlockHash,
privacyGroupId,
disposablePrivateState,
privateWorldStateUpdater,
result);
privateWorldStateUpdater.commit();
disposablePrivateState.persist();
storePrivateMetadata(
pmtHash, privacyGroupId, disposablePrivateState, privateMetadataUpdater, result);
}
return result.getOutput();

@ -32,9 +32,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt;
import org.hyperledger.besu.ethereum.privacy.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.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
@ -52,7 +50,6 @@ import org.apache.tuweni.bytes.Bytes32;
public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
final Enclave enclave;
final PrivateStateStorage privateStateStorage;
final WorldStateArchive privateWorldStateArchive;
final PrivateStateRootResolver privateStateRootResolver;
PrivateTransactionProcessor privateTransactionProcessor;
@ -67,7 +64,6 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
gasCalculator,
privacyParameters.getEnclave(),
privacyParameters.getPrivateWorldStateArchive(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateStateRootResolver(),
name);
}
@ -77,28 +73,19 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
final GasCalculator gasCalculator,
final Enclave enclave,
final WorldStateArchive worldStateArchive,
final PrivateStateStorage privateStateStorage,
final PrivateStateRootResolver privateStateRootResolver) {
this(
gasCalculator,
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver,
"Privacy");
this(gasCalculator, enclave, worldStateArchive, privateStateRootResolver, "Privacy");
}
protected PrivacyPrecompiledContract(
final GasCalculator gasCalculator,
final Enclave enclave,
final WorldStateArchive worldStateArchive,
final PrivateStateStorage privateStateStorage,
final PrivateStateRootResolver privateStateRootResolver,
final String name) {
super(name, gasCalculator);
this.enclave = enclave;
this.privateWorldStateArchive = worldStateArchive;
this.privateStateStorage = privateStateStorage;
this.privateStateRootResolver = privateStateRootResolver;
}
@ -165,10 +152,9 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
LOG.debug("Processing private transaction {} in privacy group {}", pmtHash, privacyGroupId);
final Hash currentBlockHash = ((BlockHeader) messageFrame.getBlockHeader()).getHash();
final PrivateMetadataUpdater privateMetadataUpdater = messageFrame.getPrivateMetadataUpdater();
final Hash lastRootHash =
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, currentBlockHash);
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, privateMetadataUpdater);
final MutableWorldState disposablePrivateState =
privateWorldStateArchive.getMutable(lastRootHash).get();
@ -185,81 +171,42 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
pmtHash,
result.getValidationResult().getErrorMessage());
final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater();
storeTransactionReceipt(pmtHash, currentBlockHash, result, privateStateUpdater);
privateStateUpdater.commit();
privateMetadataUpdater.putTransactionReceipt(pmtHash, new PrivateTransactionReceipt(result));
return Bytes.EMPTY;
}
if (messageFrame.isPersistingPrivateState()) {
persistPrivateState(
pmtHash,
currentBlockHash,
privacyGroupId,
disposablePrivateState,
privateWorldStateUpdater,
result);
privateWorldStateUpdater.commit();
disposablePrivateState.persist();
storePrivateMetadata(
pmtHash, privacyGroupId, disposablePrivateState, privateMetadataUpdater, result);
}
return result.getOutput();
}
void persistPrivateState(
void storePrivateMetadata(
final Hash commitmentHash,
final Hash currentBlockHash,
final Bytes32 privacyGroupId,
final MutableWorldState disposablePrivateState,
final WorldUpdater privateWorldStateUpdater,
final PrivateMetadataUpdater privateMetadataUpdater,
final PrivateTransactionProcessor.Result result) {
LOG.trace(
"Persisting private state {} for privacyGroup {}",
disposablePrivateState.rootHash(),
privacyGroupId);
privateWorldStateUpdater.commit();
disposablePrivateState.persist();
final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater();
updatePrivateBlockMetadata(
commitmentHash,
currentBlockHash,
privacyGroupId,
disposablePrivateState.rootHash(),
privateStateUpdater);
maybeUpdateGroupHeadBlockMap(privacyGroupId, currentBlockHash, privateStateUpdater);
final int txStatus =
result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0;
storeTransactionReceipt(commitmentHash, currentBlockHash, result, privateStateUpdater);
privateStateUpdater.commit();
}
void storeTransactionReceipt(
final Hash pmtHash,
final Hash currentBlockHash,
final PrivateTransactionProcessor.Result result,
final PrivateStateStorage.Updater privateStateUpdater) {
final PrivateTransactionReceipt privateTransactionReceipt =
new PrivateTransactionReceipt(result);
privateStateUpdater.putTransactionReceipt(currentBlockHash, pmtHash, privateTransactionReceipt);
}
void maybeUpdateGroupHeadBlockMap(
final Bytes32 privacyGroupId,
final Hash currentBlockHash,
final PrivateStateStorage.Updater privateStateUpdater) {
new PrivateTransactionReceipt(
txStatus, result.getLogs(), result.getOutput(), result.getRevertReason());
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).orElseThrow();
if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) {
privacyGroupHeadBlockMap.put(Bytes32.wrap(privacyGroupId), currentBlockHash);
privateStateUpdater.putPrivacyGroupHeadBlockMap(
currentBlockHash, new PrivacyGroupHeadBlockMap(privacyGroupHeadBlockMap));
}
privateMetadataUpdater.putTransactionReceipt(commitmentHash, privateTransactionReceipt);
privateMetadataUpdater.updatePrivacyGroupHeadBlockMap(privacyGroupId);
privateMetadataUpdater.addPrivateTransactionMetadata(
privacyGroupId,
new PrivateTransactionMetadata(commitmentHash, disposablePrivateState.rootHash()));
}
PrivateTransactionProcessor.Result processPrivateTransaction(
@ -308,20 +255,4 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
}
return isMining;
}
void updatePrivateBlockMetadata(
final Hash markerTransactionHash,
final Hash currentBlockHash,
final Bytes32 privacyGroupId,
final Hash rootHash,
final PrivateStateStorage.Updater privateStateUpdater) {
final PrivateBlockMetadata privateBlockMetadata =
privateStateStorage
.getPrivateBlockMetadata(currentBlockHash, Bytes32.wrap(privacyGroupId))
.orElseGet(PrivateBlockMetadata::empty);
privateBlockMetadata.addPrivateTransactionMetadata(
new PrivateTransactionMetadata(markerTransactionHash, rootHash));
privateStateUpdater.putPrivateBlockMetadata(
Bytes32.wrap(currentBlockHash), Bytes32.wrap(privacyGroupId), privateBlockMetadata);
}
}

@ -270,7 +270,7 @@ public class DefaultPrivacyController implements PrivacyController {
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
.orElse(PrivacyGroupHeadBlockMap.empty());
privacyGroupHeadBlockMap
.keySet()
.forEach(
@ -377,7 +377,7 @@ public class DefaultPrivacyController implements PrivacyController {
PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockchain.getChainHeadHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
.orElse(PrivacyGroupHeadBlockMap.empty());
if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) {
Hash blockHash = privacyGroupHeadBlockMap.get(privacyGroupId);
while (blockHash != null) {
@ -391,7 +391,7 @@ public class DefaultPrivacyController implements PrivacyController {
privacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockHash)
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
.orElse(PrivacyGroupHeadBlockMap.empty());
if (privacyGroupHeadBlockMap.get(privacyGroupId) != null) {
blockHash = privacyGroupHeadBlockMap.get(privacyGroupId);
} else {

@ -34,8 +34,7 @@ import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.privacy.group.OnChainGroupManagement;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
@ -93,6 +92,9 @@ public class PrivateGroupRehydrationBlockProcessor {
final List<Transaction> transactions = block.getBody().getTransactions();
final BlockHeader blockHeader = block.getHeader();
final PrivateMetadataUpdater metadataUpdater =
new PrivateMetadataUpdater(blockHeader, privateStateStorage);
for (final Transaction transaction : transactions) {
final long remainingGasBudget = blockHeader.getGasLimit() - gasUsed;
@ -109,12 +111,12 @@ public class PrivateGroupRehydrationBlockProcessor {
final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);
if (forExecution.containsKey(transaction.getHash())) {
final PrivateTransaction privateTransaction = forExecution.get(transaction.getHash());
final Hash transactionHash = transaction.getHash();
if (forExecution.containsKey(transactionHash)) {
final PrivateTransaction privateTransaction = forExecution.get(transactionHash);
final Bytes32 privacyGroupId = Bytes32.wrap(privateTransaction.getPrivacyGroupId().get());
final Hash lastRootHash =
privateStateRootResolver.resolveLastStateRoot(
Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()),
blockHeader.getParentHash());
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, metadataUpdater);
final MutableWorldState disposablePrivateState =
privateWorldStateArchive.getMutable(lastRootHash).get();
@ -124,7 +126,7 @@ public class PrivateGroupRehydrationBlockProcessor {
LOG.debug(
"Pre-rehydrate root hash: {} for tx {}",
disposablePrivateState.rootHash(),
transaction.getHash());
transactionHash);
final PrivateTransactionProcessor.Result privateResult =
privateTransactionProcessor.processTransaction(
@ -132,24 +134,28 @@ public class PrivateGroupRehydrationBlockProcessor {
worldStateUpdater.updater(),
privateStateUpdater,
blockHeader,
transaction.getHash(),
transactionHash,
privateTransaction,
miningBeneficiary,
OperationTracer.NO_TRACING,
new BlockHashLookup(blockHeader, blockchain),
privateTransaction.getPrivacyGroupId().get());
persistPrivateState(
transaction.getHash(),
blockHeader.getHash(),
privateTransaction,
Bytes32.wrap(privateTransaction.getPrivacyGroupId().get()),
privateStateUpdater.commit();
disposablePrivateState.persist();
storePrivateMetadata(
transactionHash,
privacyGroupId,
disposablePrivateState,
privateStateUpdater,
privateStateStorage,
metadataUpdater,
privateResult);
LOG.debug("Post-rehydrate root hash: {}", disposablePrivateState.rootHash());
}
// We have to process the public transactions here, because the private transactions can
// depend on public state
final TransactionProcessor.Result result =
transactionProcessor.processTransaction(
blockchain,
@ -164,7 +170,6 @@ public class PrivateGroupRehydrationBlockProcessor {
return AbstractBlockProcessor.Result.failed();
}
worldStateUpdater.commit();
gasUsed = transaction.getGasLimit() - result.getGasRemaining() + gasUsed;
final TransactionReceipt transactionReceipt =
transactionReceiptFactory.create(result, worldState, gasUsed);
@ -175,36 +180,18 @@ public class PrivateGroupRehydrationBlockProcessor {
return AbstractBlockProcessor.Result.failed();
}
metadataUpdater.commit();
return AbstractBlockProcessor.Result.successful(receipts);
}
protected void persistPrivateState(
void storePrivateMetadata(
final Hash commitmentHash,
final Hash currentBlockHash,
final PrivateTransaction privateTransaction,
final Bytes32 privacyGroupId,
final MutableWorldState disposablePrivateState,
final WorldUpdater privateWorldStateUpdater,
final PrivateStateStorage privateStateStorage,
final PrivateMetadataUpdater privateMetadataUpdater,
final PrivateTransactionProcessor.Result result) {
LOG.trace(
"Persisting private state {} for privacyGroup {}",
disposablePrivateState.rootHash(),
privacyGroupId);
privateWorldStateUpdater.commit();
disposablePrivateState.persist();
final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater();
updatePrivateBlockMetadata(
commitmentHash,
currentBlockHash,
privacyGroupId,
disposablePrivateState.rootHash(),
privateStateUpdater,
privateStateStorage);
final int txStatus =
result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0;
@ -212,16 +199,11 @@ public class PrivateGroupRehydrationBlockProcessor {
new PrivateTransactionReceipt(
txStatus, result.getLogs(), result.getOutput(), result.getRevertReason());
privateStateUpdater.putTransactionReceipt(
currentBlockHash, commitmentHash, privateTransactionReceipt);
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).get();
if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) {
privacyGroupHeadBlockMap.put(Bytes32.wrap(privacyGroupId), currentBlockHash);
privateStateUpdater.putPrivacyGroupHeadBlockMap(
currentBlockHash, new PrivacyGroupHeadBlockMap(privacyGroupHeadBlockMap));
}
privateStateUpdater.commit();
privateMetadataUpdater.putTransactionReceipt(commitmentHash, privateTransactionReceipt);
privateMetadataUpdater.updatePrivacyGroupHeadBlockMap(privacyGroupId);
privateMetadataUpdater.addPrivateTransactionMetadata(
privacyGroupId,
new PrivateTransactionMetadata(commitmentHash, disposablePrivateState.rootHash()));
}
protected void maybeInjectDefaultManagementAndProxy(
@ -253,23 +235,6 @@ public class PrivateGroupRehydrationBlockProcessor {
}
}
private void updatePrivateBlockMetadata(
final Hash markerTransactionHash,
final Hash currentBlockHash,
final Bytes32 privacyGroupId,
final Hash rootHash,
final PrivateStateStorage.Updater privateStateUpdater,
final PrivateStateStorage privateStateStorage) {
final PrivateBlockMetadata privateBlockMetadata =
privateStateStorage
.getPrivateBlockMetadata(currentBlockHash, Bytes32.wrap(privacyGroupId))
.orElseGet(PrivateBlockMetadata::empty);
privateBlockMetadata.addPrivateTransactionMetadata(
new PrivateTransactionMetadata(markerTransactionHash, rootHash));
privateStateUpdater.putPrivateBlockMetadata(
Bytes32.wrap(currentBlockHash), Bytes32.wrap(privacyGroupId), privateBlockMetadata);
}
private boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,

@ -88,16 +88,26 @@ public class PrivateStateRehydration {
.updater()
.putPrivacyGroupHeadBlockMap(
getBlockHashForIndex(0, privateTransactionWithMetadataList),
PrivacyGroupHeadBlockMap.EMPTY)
PrivacyGroupHeadBlockMap.empty())
.commit();
}
final LinkedHashMap<Hash, PrivateTransaction> pmtHashToPrivateTransactionMap =
new LinkedHashMap<>();
for (int j = 0; j < privateTransactionWithMetadataList.size(); j++) {
final PrivateTransactionWithMetadata transactionWithMetadata =
privateTransactionWithMetadataList.get(j);
pmtHashToPrivateTransactionMap.put(
transactionWithMetadata.getPrivateTransactionMetadata().getPrivacyMarkerTransactionHash(),
transactionWithMetadata.getPrivateTransaction());
}
for (int i = 0; i < privateTransactionWithMetadataList.size(); i++) {
// find out which block this transaction is in
final Hash blockHash = getBlockHashForIndex(i, privateTransactionWithMetadataList);
// if there are multiple pmts in the list we can increment our index i. At the end of the
// while loop i will be the index of the last PMT (for this group) that is in this block.
// At the end of the while loop i will be the index of the last PMT (for this group) that is
// in this block.
while (i + 1 < privateTransactionWithMetadataList.size()
&& blockHash.equals(getBlockHashForIndex(i + 1, privateTransactionWithMetadataList))) {
i++;
@ -127,8 +137,7 @@ public class PrivateStateRehydration {
.map(Transaction::getHash)
.collect(Collectors.toList()));
final ProtocolSpec protocolSpec =
protocolSchedule.getByBlockNumber(blockchain.getBlockHeader(blockHash).get().getNumber());
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(blockHeader.getNumber());
final PrivateGroupRehydrationBlockProcessor privateGroupRehydrationBlockProcessor =
new PrivateGroupRehydrationBlockProcessor(
protocolSpec.getTransactionProcessor(),
@ -145,18 +154,6 @@ public class PrivateStateRehydration {
.flatMap(publicWorldStateArchive::getMutable)
.orElseThrow(RuntimeException::new);
// enclave cache for private block rehydration
final LinkedHashMap<Hash, PrivateTransaction> enclaveMap = new LinkedHashMap<>();
for (int j = 0; j < privateTransactionWithMetadataList.size(); j++) {
final PrivateTransactionWithMetadata transactionWithMetadata =
privateTransactionWithMetadataList.get(j);
enclaveMap.put(
transactionWithMetadata
.getPrivateTransactionMetadata()
.getPrivacyMarkerTransactionHash(),
transactionWithMetadata.getPrivateTransaction());
}
privateGroupRehydrationBlockProcessor.processBlock(
blockchain,
publicWorldState,
@ -164,7 +161,7 @@ public class PrivateStateRehydration {
privateStateStorage,
privateStateRootResolver,
block,
enclaveMap,
pmtHashToPrivateTransactionMap,
block.getBody().getOmmers());
// check the resulting private state against the state in the meta data
@ -217,7 +214,7 @@ public class PrivateStateRehydration {
final PrivacyGroupHeadBlockMap thePrivacyGroupHeadBlockMap =
privateStateStorage
.getPrivacyGroupHeadBlockMap(theBlockHeader.getHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY);
.orElse(PrivacyGroupHeadBlockMap.empty());
final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater();
thePrivacyGroupHeadBlockMap.put(privacyGroupId, hashOfLastBlockWithPmt);
privateStateUpdater.putPrivacyGroupHeadBlockMap(

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.privacy;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
@ -33,6 +34,30 @@ public class PrivateStateRootResolver {
this.privateStateStorage = privateStateStorage;
}
public Hash resolveLastStateRoot(
final Bytes32 privacyGroupId, final PrivateMetadataUpdater privateMetadataUpdater) {
final PrivateBlockMetadata privateBlockMetadata =
privateMetadataUpdater.getPrivateBlockMetadata(privacyGroupId);
if (privateBlockMetadata != null) {
return privateBlockMetadata.getLatestStateRoot().get();
} else {
final Hash blockHashForLastBlockWithTx =
privateMetadataUpdater.getPrivacyGroupHeadBlockMap().get(privacyGroupId);
if (blockHashForLastBlockWithTx != null) {
return privateStateStorage
.getPrivateBlockMetadata(blockHashForLastBlockWithTx, privacyGroupId)
.flatMap(PrivateBlockMetadata::getLatestStateRoot)
.orElseThrow(
() ->
new RuntimeException(
"Privacy inconsistent state: PrivateBlockMetadata does not exist for Block "
+ blockHashForLastBlockWithTx));
} else {
return EMPTY_ROOT_HASH;
}
}
}
public Hash resolveLastStateRoot(final Bytes32 privacyGroupId, final Hash blockHash) {
final Optional<PrivateBlockMetadata> privateBlockMetadataOptional =
privateStateStorage.getPrivateBlockMetadata(blockHash, privacyGroupId);
@ -60,7 +85,11 @@ public class PrivateStateRootResolver {
privateStateStorage
.getPrivateBlockMetadata(blockHashForLastBlockWithTx, privacyGroupId)
.flatMap(PrivateBlockMetadata::getLatestStateRoot)
.orElse(EMPTY_ROOT_HASH);
.orElseThrow(
() ->
new RuntimeException(
"Privacy inconsistent state: PrivateBlockMetadata does not exist for Block "
+ blockHashForLastBlockWithTx));
} else {
// First transaction for this PG
lastRootHash = EMPTY_ROOT_HASH;

@ -31,8 +31,9 @@ import org.apache.tuweni.bytes.Bytes32;
public class PrivacyGroupHeadBlockMap implements Map<Bytes32, Hash> {
private final HashMap<Bytes32, Hash> map;
public static final PrivacyGroupHeadBlockMap EMPTY =
new PrivacyGroupHeadBlockMap(Collections.emptyMap());
public static final PrivacyGroupHeadBlockMap empty() {
return new PrivacyGroupHeadBlockMap(Collections.emptyMap());
}
public PrivacyGroupHeadBlockMap(final Map<Bytes32, Hash> map) {
this.map = new HashMap<>(map);

@ -0,0 +1,83 @@
/*
* 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.storage;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt;
import java.util.HashMap;
import java.util.Map;
import org.apache.tuweni.bytes.Bytes32;
public class PrivateMetadataUpdater {
private final PrivateStateStorage privateStateKeyValueStorage;
private final BlockHeader blockHeader;
private final PrivateStateStorage.Updater updater;
private final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap;
private final Map<Bytes32, PrivateBlockMetadata> privateBlockMetadataMap = new HashMap<>();
public PrivateMetadataUpdater(
final BlockHeader blockHeader, final PrivateStateStorage keyValueStorage) {
this.privateStateKeyValueStorage = keyValueStorage;
this.blockHeader = blockHeader;
this.updater = privateStateKeyValueStorage.updater();
this.privacyGroupHeadBlockMap =
privateStateKeyValueStorage
.getPrivacyGroupHeadBlockMap(blockHeader.getParentHash())
.orElse(PrivacyGroupHeadBlockMap.empty());
}
public PrivateBlockMetadata getPrivateBlockMetadata(final Bytes32 privacyGroupId) {
return privateBlockMetadataMap.get(privacyGroupId);
}
public PrivacyGroupHeadBlockMap getPrivacyGroupHeadBlockMap() {
return privacyGroupHeadBlockMap;
}
public void putTransactionReceipt(
final Bytes32 transactionHash, final PrivateTransactionReceipt receipt) {
updater.putTransactionReceipt(blockHeader.getBlockHash(), transactionHash, receipt);
}
public void addPrivateTransactionMetadata(
final Bytes32 privacyGroupId, final PrivateTransactionMetadata metadata) {
PrivateBlockMetadata privateBlockMetadata = privateBlockMetadataMap.get(privacyGroupId);
if (privateBlockMetadata == null) {
privateBlockMetadata = PrivateBlockMetadata.empty();
}
privateBlockMetadata.addPrivateTransactionMetadata(metadata);
privateBlockMetadataMap.put(privacyGroupId, privateBlockMetadata);
}
public void updatePrivacyGroupHeadBlockMap(final Bytes32 privacyGroupId) {
privacyGroupHeadBlockMap.put(privacyGroupId, blockHeader.getHash());
}
public void commit() {
if (privacyGroupHeadBlockMap.size() > 0) {
updater.putPrivacyGroupHeadBlockMap(blockHeader.getHash(), privacyGroupHeadBlockMap);
}
privateBlockMetadataMap.entrySet().stream()
.forEach(
entry ->
updater.putPrivateBlockMetadata(
blockHeader.getHash(), entry.getKey(), entry.getValue()));
updater.commit();
}
}

@ -166,7 +166,7 @@ public class PrivateStorageMigration {
new PrivacyGroupHeadBlockMap(
privateStateStorage
.getPrivacyGroupHeadBlockMap(blockHeader.getParentHash())
.orElse(PrivacyGroupHeadBlockMap.EMPTY));
.orElse(PrivacyGroupHeadBlockMap.empty()));
privateStateStorage
.updater()

@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.mainnet.AbstractMessageProcessor;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.vm.internal.MemoryEntry;
import org.hyperledger.besu.ethereum.vm.operations.ReturnStack;
@ -228,6 +229,7 @@ public class MessageFrame {
private final Deque<MessageFrame> messageFrameStack;
private final Address miningBeneficiary;
private final Boolean isPersistingPrivateState;
private final PrivateMetadataUpdater privateMetadataUpdater;
private Optional<Bytes> revertReason;
// as defined on https://eips.ethereum.org/EIPS/eip-2315
@ -272,6 +274,7 @@ public class MessageFrame {
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup,
final Boolean isPersistingPrivateState,
final PrivateMetadataUpdater privateMetadataUpdater,
final Hash transactionHash,
final Optional<Bytes> revertReason,
final int maxStackSize) {
@ -309,6 +312,7 @@ public class MessageFrame {
this.completer = completer;
this.miningBeneficiary = miningBeneficiary;
this.isPersistingPrivateState = isPersistingPrivateState;
this.privateMetadataUpdater = privateMetadataUpdater;
this.transactionHash = transactionHash;
this.revertReason = revertReason;
}
@ -1024,6 +1028,10 @@ public class MessageFrame {
return isPersistingPrivateState;
}
public PrivateMetadataUpdater getPrivateMetadataUpdater() {
return privateMetadataUpdater;
}
/**
* Returns the transaction hash of the transaction being processed
*
@ -1083,6 +1091,7 @@ public class MessageFrame {
private Address miningBeneficiary;
private BlockHashLookup blockHashLookup;
private Boolean isPersistingPrivateState = false;
private PrivateMetadataUpdater privateMetadataUpdater = null;
private Hash transactionHash;
private Optional<Bytes> reason = Optional.empty();
private ReturnStack returnStack = new ReturnStack();
@ -1208,6 +1217,11 @@ public class MessageFrame {
return this;
}
public Builder privateMetadataUpdater(final PrivateMetadataUpdater privateMetadataUpdater) {
this.privateMetadataUpdater = privateMetadataUpdater;
return this;
}
public Builder transactionHash(final Hash transactionHash) {
this.transactionHash = transactionHash;
return this;
@ -1270,6 +1284,7 @@ public class MessageFrame {
miningBeneficiary,
blockHashLookup,
isPersistingPrivateState,
privateMetadataUpdater,
transactionHash,
reason,
maxStackSize);

@ -19,6 +19,7 @@ import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.V
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -103,18 +104,20 @@ public class PrivacyBlockProcessorTest {
.contains(expected);
verify(blockProcessor)
.processBlock(
blockchain,
mutableWorldState,
firstBlock.getHeader(),
firstBlock.getBody().getTransactions(),
firstBlock.getBody().getOmmers());
eq(blockchain),
eq(mutableWorldState),
eq(firstBlock.getHeader()),
eq(firstBlock.getBody().getTransactions()),
eq(firstBlock.getBody().getOmmers()),
any());
verify(blockProcessor)
.processBlock(
blockchain,
mutableWorldState,
secondBlock.getHeader(),
secondBlock.getBody().getTransactions(),
secondBlock.getBody().getOmmers());
eq(blockchain),
eq(mutableWorldState),
eq(secondBlock.getHeader()),
eq(secondBlock.getBody().getTransactions()),
eq(secondBlock.getBody().getOmmers()),
any());
}
@SuppressWarnings({"rawtypes", "unchecked"})
@ -164,11 +167,12 @@ public class PrivacyBlockProcessorTest {
privacyBlockProcessor.processBlock(blockchain, mutableWorldState, secondBlock);
verify(blockProcessor)
.processBlock(
blockchain,
mutableWorldState,
secondBlock.getHeader(),
secondBlock.getBody().getTransactions(),
secondBlock.getBody().getOmmers());
eq(blockchain),
eq(mutableWorldState),
eq(secondBlock.getHeader()),
eq(secondBlock.getBody().getTransactions()),
eq(secondBlock.getBody().getOmmers()),
any());
}
private MutableWorldState mockPrivateStateArchive() {

@ -46,6 +46,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.VersionedPrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
@ -109,7 +110,7 @@ public class OnChainPrivacyPrecompiledContractTest {
final PrivateStateStorage.Updater storageUpdater = mock(PrivateStateStorage.Updater.class);
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any()))
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY));
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.empty()));
when(privateStateStorage.getPrivateBlockMetadata(any(), any())).thenReturn(Optional.empty());
when(storageUpdater.putPrivateBlockMetadata(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
@ -133,6 +134,10 @@ public class OnChainPrivacyPrecompiledContractTest {
when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis));
when(messageFrame.getBlockchain()).thenReturn(blockchain);
when(messageFrame.getBlockHeader()).thenReturn(block.getHeader());
final PrivateMetadataUpdater privateMetadataUpdater = mock(PrivateMetadataUpdater.class);
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = mock(PrivacyGroupHeadBlockMap.class);
when(messageFrame.getPrivateMetadataUpdater()).thenReturn(privateMetadataUpdater);
when(privateMetadataUpdater.getPrivacyGroupHeadBlockMap()).thenReturn(privacyGroupHeadBlockMap);
}
@Test
@ -265,7 +270,6 @@ public class OnChainPrivacyPrecompiledContractTest {
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
contract.setPrivateTransactionProcessor(
@ -305,10 +309,6 @@ public class OnChainPrivacyPrecompiledContractTest {
private OnChainPrivacyPrecompiledContract buildPrivacyPrecompiledContract(final Enclave enclave) {
return new OnChainPrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateRootResolver);
}
}

@ -47,6 +47,8 @@ import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
@ -56,11 +58,11 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@ -78,6 +80,7 @@ public class PrivacyPrecompiledContractTest {
final String PAYLOAD_TEST_PRIVACY_GROUP_ID = "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o=";
private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class);
final PrivateMetadataUpdater privateMetadataUpdater = mock(PrivateMetadataUpdater.class);
final PrivateStateRootResolver privateStateRootResolver =
new PrivateStateRootResolver(privateStateStorage);
@ -108,19 +111,10 @@ public class PrivacyPrecompiledContractTest {
when(worldStateArchive.getMutable()).thenReturn(mutableWorldState);
when(worldStateArchive.getMutable(any())).thenReturn(Optional.of(mutableWorldState));
final PrivateStateStorage.Updater storageUpdater = mock(PrivateStateStorage.Updater.class);
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any()))
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY));
when(privateStateStorage.getPrivateBlockMetadata(any(), any())).thenReturn(Optional.empty());
when(storageUpdater.putPrivateBlockMetadata(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(storageUpdater.putPrivacyGroupHeadBlockMap(nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(storageUpdater.putTransactionReceipt(
nullable(Bytes32.class), nullable(Bytes32.class), any()))
.thenReturn(storageUpdater);
when(privateStateStorage.updater()).thenReturn(storageUpdater);
when(privateMetadataUpdater.getPrivacyGroupHeadBlockMap())
.thenReturn(PrivacyGroupHeadBlockMap.empty());
when(privateMetadataUpdater.getPrivateBlockMetadata(any()))
.thenReturn(new PrivateBlockMetadata(Collections.emptyList()));
messageFrame = mock(MessageFrame.class);
blockchain = mock(Blockchain.class);
@ -134,6 +128,10 @@ public class PrivacyPrecompiledContractTest {
when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis));
when(messageFrame.getBlockchain()).thenReturn(blockchain);
when(messageFrame.getBlockHeader()).thenReturn(block.getHeader());
final PrivateMetadataUpdater privateMetadataUpdater = mock(PrivateMetadataUpdater.class);
when(messageFrame.getPrivateMetadataUpdater()).thenReturn(privateMetadataUpdater);
when(privateMetadataUpdater.getPrivacyGroupHeadBlockMap())
.thenReturn(PrivacyGroupHeadBlockMap.empty());
}
@Test
@ -283,7 +281,6 @@ public class PrivacyPrecompiledContractTest {
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
contract.setPrivateTransactionProcessor(
@ -315,10 +312,6 @@ public class PrivacyPrecompiledContractTest {
private PrivacyPrecompiledContract buildPrivacyPrecompiledContract(final Enclave enclave) {
return new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(),
enclave,
worldStateArchive,
privateStateStorage,
privateStateRootResolver);
new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateRootResolver);
}
}

@ -0,0 +1,87 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.privacy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.InMemoryPrivacyStorageProvider;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Test;
public class PrivateMetadataUpdaterTest {
private PrivateMetadataUpdater updater;
private BlockHeader blockHeader;
private PrivateStateStorage privateStateStorage;
private Hash hashBlockOne;
private Bytes32 privacyGroupId;
private Hash stateRoot;
private Hash pmtHash;
@Before
public void before() {
blockHeader = mock(BlockHeader.class);
privateStateStorage = new InMemoryPrivacyStorageProvider().createPrivateStateStorage();
final Hash hashBlockZero = Hash.ZERO;
when(blockHeader.getParentHash()).thenReturn(hashBlockZero);
updater = new PrivateMetadataUpdater(blockHeader, privateStateStorage);
hashBlockOne =
Hash.fromHexString("1111111111111111111111111111111111111111111111111111111111111111");
stateRoot =
Hash.fromHexString("2222222222222222222222222222222222222222222222222222222222222222");
privacyGroupId =
Bytes32.fromHexString("3333333333333333333333333333333333333333333333333333333333333333");
}
@Test
public void returnsEmptyPrivateGroupHeadBlockMapForUnknownBlock() {
assertThat(updater.getPrivacyGroupHeadBlockMap()).isEqualTo(PrivacyGroupHeadBlockMap.empty());
}
@Test
public void addingMetadataSuccessfull() {
when(blockHeader.getHash()).thenReturn(hashBlockOne);
pmtHash = Hash.ZERO;
final PrivateTransactionMetadata expected = new PrivateTransactionMetadata(pmtHash, stateRoot);
updater.addPrivateTransactionMetadata(privacyGroupId, expected);
updater.commit();
final Optional<PrivateBlockMetadata> privateBlockMetadata =
privateStateStorage.getPrivateBlockMetadata(hashBlockOne, privacyGroupId);
assertThat(privateBlockMetadata.get().getLatestStateRoot().get()).isEqualTo(stateRoot);
}
@Test
public void updatesPrivacyGroupHeadBlockMap() {
when(blockHeader.getHash()).thenReturn(hashBlockOne);
updater.updatePrivacyGroupHeadBlockMap(privacyGroupId);
updater.commit();
final PrivacyGroupHeadBlockMap actual =
privateStateStorage.getPrivacyGroupHeadBlockMap(hashBlockOne).get();
assertThat(actual.get(privacyGroupId)).isEqualTo(hashBlockOne);
}
}

@ -45,13 +45,17 @@ import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.storage.LegacyPrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -125,7 +129,7 @@ public class PrivateStorageMigrationTest {
// create existing map at block hash 'zero' (pre-genesis)
final PrivacyGroupHeadBlockMap existingPgHeadMap =
createPrivacyGroupHeadBlockInitialMap(PRIVACY_GROUP_BYTES);
createPrivacyGroupHeadBlockInitialMapAndMetadata(PRIVACY_GROUP_BYTES, EMPTY_ROOT_HASH);
migration.migratePrivateStorage();
@ -154,7 +158,8 @@ public class PrivateStorageMigrationTest {
public void failedMigrationThrowsErrorAndDoesNotBumpSchemaVersion() {
final Transaction privacyMarkerTransaction = createPrivacyMarkerTransaction();
mockBlockchainWithPrivacyMarkerTransaction(privacyMarkerTransaction);
createPrivacyGroupHeadBlockInitialMap(PRIVACY_GROUP_BYTES);
final Hash rootHashOtherThanZero = Hash.wrap(Bytes32.fromHexStringLenient("1"));
createPrivacyGroupHeadBlockInitialMapAndMetadata(PRIVACY_GROUP_BYTES, rootHashOtherThanZero);
// final state root won't match the legacy state root
when(legacyPrivateStateStorage.getLatestStateRoot(any())).thenReturn(Optional.of(Hash.ZERO));
@ -218,14 +223,18 @@ public class PrivateStorageMigrationTest {
assertThat(processedTxs).hasSize(2);
}
private PrivacyGroupHeadBlockMap createPrivacyGroupHeadBlockInitialMap(
final Bytes32 privacyGroupBytes) {
private PrivacyGroupHeadBlockMap createPrivacyGroupHeadBlockInitialMapAndMetadata(
final Bytes32 privacyGroupBytes, final Hash rootHash) {
final PrivacyGroupHeadBlockMap existingPgHeadMap =
new PrivacyGroupHeadBlockMap(Map.of(privacyGroupBytes, Hash.ZERO));
privateStateStorage
.updater()
.putPrivacyGroupHeadBlockMap(Hash.ZERO, existingPgHeadMap)
.commit();
final PrivateStateStorage.Updater updater = privateStateStorage.updater();
updater.putPrivacyGroupHeadBlockMap(Hash.ZERO, existingPgHeadMap);
updater.putPrivateBlockMetadata(
Hash.ZERO,
privacyGroupBytes,
new PrivateBlockMetadata(
Arrays.asList(new PrivateTransactionMetadata(Hash.ZERO, rootHash))));
updater.commit();
return existingPgHeadMap;
}

Loading…
Cancel
Save