Skip to content

Commit

Permalink
Storing private tx receipts indexed by PMT hashes (#645)
Browse files Browse the repository at this point in the history
* Private tx receipt stored using pmt hash as key

Signed-off-by: Lucas Saldanha <lucas.saldanha@consensys.net>
  • Loading branch information
lucassaldanha authored Apr 3, 2020
1 parent 031c8cb commit 82c8909
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand All @@ -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(),
Expand All @@ -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");

Expand All @@ -168,6 +170,16 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
}
}

private Optional<? extends PrivateTransactionReceipt> 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<PrivateTransaction> findPrivateTransactionInEnclave(
final String payloadKey,
final Hash pmtTransactionHash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -209,16 +207,15 @@ protected void persistPrivateState(
privateStateUpdater,
privateStateStorage);

final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo));

final int txStatus =
result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0;

final PrivateTransactionReceipt privateTransactionReceipt =
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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public PrivateStateKeyValueStorage(final KeyValueStorage keyValueStorage) {

@Override
public Optional<PrivateTransactionReceipt> 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)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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 =
Expand Down Expand Up @@ -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());
Expand All @@ -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);
}
}

0 comments on commit 82c8909

Please sign in to comment.