From 82c89096bd78c80913d76f1c8bf5f310c887f930 Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Fri, 3 Apr 2020 19:30:56 +1300 Subject: [PATCH] Storing private tx receipts indexed by PMT hashes (#645) * Private tx receipt stored using pmt hash as key Signed-off-by: Lucas Saldanha --- .../priv/PrivGetTransactionReceipt.java | 36 +++++--- .../privacy/PrivacyPrecompiledContract.java | 8 +- ...PrivateGroupRehydrationBlockProcessor.java | 7 +- .../storage/PrivateStateKeyValueStorage.java | 4 +- .../PrivacyPrecompiledContractTest.java | 84 +++++++++++-------- 5 files changed, 80 insertions(+), 59 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java index 9c8d0c3e84d..2d18a67a349 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java @@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -130,14 +131,15 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Calculated contractAddress: {}", contractAddress); - final Bytes rlpEncoded = RLP.encode(privateTransaction::writeTo); - final Bytes32 txHash = org.hyperledger.besu.crypto.Hash.keccak256(rlpEncoded); - LOG.trace("Calculated private transaction hash: {}", txHash); - - final PrivateTransactionReceipt privateTransactioReceipt = - privacyParameters - .getPrivateStateStorage() - .getTransactionReceipt(blockHash, txHash) + final PrivateStateStorage privateStateStorage = privacyParameters.getPrivateStateStorage(); + final PrivateTransactionReceipt privateTransactionReceipt = + privateStateStorage + .getTransactionReceipt(blockHash, pmtTransactionHash) + // backwards compatibility - private receipts indexed by private transaction hash key + .or( + () -> + findPrivateReceiptByPrivateTxHash( + privateStateStorage, blockHash, privateTransaction)) .orElse(PrivateTransactionReceipt.FAILED); LOG.trace("Processed private transaction receipt"); @@ -147,8 +149,8 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { contractAddress, privateTransaction.getSender().toString(), privateTransaction.getTo().map(Address::toString).orElse(null), - privateTransactioReceipt.getLogs(), - privateTransactioReceipt.getOutput(), + privateTransactionReceipt.getLogs(), + privateTransactionReceipt.getOutput(), blockHash, blockNumber, pmtLocation.getTransactionIndex(), @@ -157,8 +159,8 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { privateTransaction.getPrivateFrom(), privateTransaction.getPrivateFor().orElse(null), privateTransaction.getPrivacyGroupId().orElse(null), - privateTransactioReceipt.getRevertReason().orElse(null), - Quantity.create(privateTransactioReceipt.getStatus())); + privateTransactionReceipt.getRevertReason().orElse(null), + Quantity.create(privateTransactionReceipt.getStatus())); LOG.trace("Created Private Transaction Receipt Result from given Transaction Hash"); @@ -168,6 +170,16 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } } + private Optional findPrivateReceiptByPrivateTxHash( + final PrivateStateStorage privateStateStorage, + final Hash blockHash, + final PrivateTransaction privateTransaction) { + final Bytes rlpEncoded = RLP.encode(privateTransaction::writeTo); + final Bytes32 txHash = org.hyperledger.besu.crypto.Hash.keccak256(rlpEncoded); + + return privateStateStorage.getTransactionReceipt(blockHash, txHash); + } + private Optional findPrivateTransactionInEnclave( final String payloadKey, final Hash pmtTransactionHash, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index e534f25010a..35ca018f377 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.ethereum.mainnet.precompiles.privacy; -import static org.hyperledger.besu.crypto.Hash.keccak256; - import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.EnclaveIOException; @@ -39,7 +37,6 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; -import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.MessageFrame; @@ -200,9 +197,8 @@ void persistPrivateState( new PrivateTransactionReceipt( txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); - final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); - - privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt); + privateStateUpdater.putTransactionReceipt( + currentBlockHash, commitmentHash, privateTransactionReceipt); maybeUpdateGroupHeadBlockMap(privacyGroupId, currentBlockHash, privateStateUpdater); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java index 15dfe1be9c0..ac065ada441 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.privacy; -import static org.hyperledger.besu.crypto.Hash.keccak256; import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -40,7 +39,6 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; -import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.OperationTracer; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -209,8 +207,6 @@ protected void persistPrivateState( privateStateUpdater, privateStateStorage); - final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); - final int txStatus = result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0; @@ -218,7 +214,8 @@ protected void persistPrivateState( new PrivateTransactionReceipt( txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); - privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt); + privateStateUpdater.putTransactionReceipt( + currentBlockHash, commitmentHash, privateTransactionReceipt); final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).get(); if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java index 38a18fa0089..4f60d86b9df 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java @@ -50,8 +50,8 @@ public PrivateStateKeyValueStorage(final KeyValueStorage keyValueStorage) { @Override public Optional getTransactionReceipt( - final Bytes32 blockHash, final Bytes32 txHash) { - final Bytes blockHashTxHash = Bytes.concatenate(blockHash, txHash); + final Bytes32 blockHash, final Bytes32 pmtHash) { + final Bytes blockHashTxHash = Bytes.concatenate(blockHash, pmtHash); return get(blockHashTxHash, TX_RECEIPT_SUFFIX) .map(b -> PrivateTransactionReceipt.readFrom(new BytesValueRLPInput(b, false))); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index cdee0c2693f..643f6d37470 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -17,8 +17,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.enclave.Enclave; @@ -28,6 +30,7 @@ import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Log; import org.hyperledger.besu.ethereum.core.MutableWorldState; @@ -37,6 +40,7 @@ import org.hyperledger.besu.ethereum.mainnet.SpuriousDragonGasCalculator; 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.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; @@ -59,14 +63,20 @@ public class PrivacyPrecompiledContractTest { @Rule public final TemporaryFolder temp = new TemporaryFolder(); + private final String PAYLOAD_TEST_PRIVACY_GROUP_ID = + "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o="; + private final String DEFAULT_OUTPUT = "0x01"; private final String actual = "Test String"; private final Bytes key = Bytes.wrap(actual.getBytes(UTF_8)); + private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); + private final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class); + private final PrivateStateStorage.Updater storageUpdater = + mock(PrivateStateStorage.Updater.class); + private final Enclave enclave = mock(Enclave.class); + private MessageFrame messageFrame; private Blockchain blockchain; - private final String DEFAULT_OUTPUT = "0x01"; - final String PAYLOAD_TEST_PRIVACY_GROUP_ID = "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o="; - private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); - final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class); + private PrivacyPrecompiledContract precompiledContract; private PrivateTransactionProcessor mockPrivateTxProcessor() { final PrivateTransactionProcessor mockPrivateTransactionProcessor = @@ -98,7 +108,6 @@ public void setUp() { 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()); @@ -124,55 +133,62 @@ public void setUp() { when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis)); when(messageFrame.getBlockchain()).thenReturn(blockchain); when(messageFrame.getBlockHeader()).thenReturn(block.getHeader()); - } - @Test - public void testPayloadFoundInEnclave() { - final Enclave enclave = mock(Enclave.class); - final PrivacyPrecompiledContract contract = + precompiledContract = new PrivacyPrecompiledContract( new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage); - contract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); - - BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); - PrivateTransactionDataFixture.privateTransactionBesu().writeTo(bytesValueRLPOutput); + precompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); + } - final ReceiveResponse response = - new ReceiveResponse( - bytesValueRLPOutput.encoded().toBase64String().getBytes(UTF_8), - PAYLOAD_TEST_PRIVACY_GROUP_ID, - null); - when(enclave.receive(any(String.class))).thenReturn(response); + @Test + public void testPayloadFoundInEnclave() { + mockEnclaveWithPrivateTransaction(); - final Bytes actual = contract.compute(key, messageFrame); + final Bytes actual = precompiledContract.compute(key, messageFrame); assertThat(actual).isEqualTo(Bytes.fromHexString(DEFAULT_OUTPUT)); } @Test public void testPayloadNotFoundInEnclave() { - final Enclave enclave = mock(Enclave.class); - - final PrivacyPrecompiledContract contract = - new PrivacyPrecompiledContract( - new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage); - when(enclave.receive(any(String.class))).thenThrow(EnclaveClientException.class); - final Bytes expected = contract.compute(key, messageFrame); + final Bytes expected = precompiledContract.compute(key, messageFrame); assertThat(expected).isEqualTo(Bytes.EMPTY); } @Test(expected = RuntimeException.class) public void testEnclaveDown() { - final Enclave enclave = mock(Enclave.class); + when(enclave.receive(any(String.class))).thenThrow(new RuntimeException()); - final PrivacyPrecompiledContract contract = - new PrivacyPrecompiledContract( - new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage); + precompiledContract.compute(key, messageFrame); + } - when(enclave.receive(any(String.class))).thenThrow(new RuntimeException()); + @Test + public void processTransactionPersistingStateCreatesPrivateReceipt() { + final Hash expectedPmtHash = Hash.wrap(Bytes32.random()); + final Hash expectedBlockHash = ((BlockHeader) messageFrame.getBlockHeader()).getHash(); + + mockEnclaveWithPrivateTransaction(); + when(messageFrame.isPersistingPrivateState()).thenReturn(true); + when(messageFrame.getTransactionHash()).thenReturn(expectedPmtHash); + + precompiledContract.compute(key, messageFrame); - contract.compute(key, messageFrame); + verify(storageUpdater) + .putTransactionReceipt( + eq(expectedBlockHash), eq(expectedPmtHash), any(PrivateTransactionReceipt.class)); + } + + private void mockEnclaveWithPrivateTransaction() { + final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput(); + PrivateTransactionDataFixture.privateTransactionBesu().writeTo(bytesValueRLPOutput); + + final ReceiveResponse response = + new ReceiveResponse( + bytesValueRLPOutput.encoded().toBase64String().getBytes(UTF_8), + PAYLOAD_TEST_PRIVACY_GROUP_ID, + null); + when(enclave.receive(any(String.class))).thenReturn(response); } }