Storing private tx receipts indexed by PMT hashes (#645)

* Private tx receipt stored using pmt hash as key

Signed-off-by: Lucas Saldanha <lucas.saldanha@consensys.net>
pull/658/head
Lucas Saldanha 5 years ago committed by GitHub
parent 031c8cba5a
commit 82c89096bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java
  2. 8
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java
  3. 7
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java
  4. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivateStateKeyValueStorage.java
  5. 84
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java

@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionWithMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; 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.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLP;
@ -130,14 +131,15 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
LOG.trace("Calculated contractAddress: {}", contractAddress); LOG.trace("Calculated contractAddress: {}", contractAddress);
final Bytes rlpEncoded = RLP.encode(privateTransaction::writeTo); final PrivateStateStorage privateStateStorage = privacyParameters.getPrivateStateStorage();
final Bytes32 txHash = org.hyperledger.besu.crypto.Hash.keccak256(rlpEncoded); final PrivateTransactionReceipt privateTransactionReceipt =
LOG.trace("Calculated private transaction hash: {}", txHash); privateStateStorage
.getTransactionReceipt(blockHash, pmtTransactionHash)
final PrivateTransactionReceipt privateTransactioReceipt = // backwards compatibility - private receipts indexed by private transaction hash key
privacyParameters .or(
.getPrivateStateStorage() () ->
.getTransactionReceipt(blockHash, txHash) findPrivateReceiptByPrivateTxHash(
privateStateStorage, blockHash, privateTransaction))
.orElse(PrivateTransactionReceipt.FAILED); .orElse(PrivateTransactionReceipt.FAILED);
LOG.trace("Processed private transaction receipt"); LOG.trace("Processed private transaction receipt");
@ -147,8 +149,8 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
contractAddress, contractAddress,
privateTransaction.getSender().toString(), privateTransaction.getSender().toString(),
privateTransaction.getTo().map(Address::toString).orElse(null), privateTransaction.getTo().map(Address::toString).orElse(null),
privateTransactioReceipt.getLogs(), privateTransactionReceipt.getLogs(),
privateTransactioReceipt.getOutput(), privateTransactionReceipt.getOutput(),
blockHash, blockHash,
blockNumber, blockNumber,
pmtLocation.getTransactionIndex(), pmtLocation.getTransactionIndex(),
@ -157,8 +159,8 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
privateTransaction.getPrivateFrom(), privateTransaction.getPrivateFrom(),
privateTransaction.getPrivateFor().orElse(null), privateTransaction.getPrivateFor().orElse(null),
privateTransaction.getPrivacyGroupId().orElse(null), privateTransaction.getPrivacyGroupId().orElse(null),
privateTransactioReceipt.getRevertReason().orElse(null), privateTransactionReceipt.getRevertReason().orElse(null),
Quantity.create(privateTransactioReceipt.getStatus())); Quantity.create(privateTransactionReceipt.getStatus()));
LOG.trace("Created Private Transaction Receipt Result from given Transaction Hash"); LOG.trace("Created Private Transaction Receipt Result from given Transaction Hash");
@ -168,6 +170,16 @@ public class PrivGetTransactionReceipt implements JsonRpcMethod {
} }
} }
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( private Optional<PrivateTransaction> findPrivateTransactionInEnclave(
final String payloadKey, final String payloadKey,
final Hash pmtTransactionHash, final Hash pmtTransactionHash,

@ -14,8 +14,6 @@
*/ */
package org.hyperledger.besu.ethereum.mainnet.precompiles.privacy; 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.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException; import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.EnclaveIOException; import org.hyperledger.besu.enclave.EnclaveIOException;
@ -39,7 +37,6 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; 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.DebugOperationTracer;
import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.GasCalculator;
import org.hyperledger.besu.ethereum.vm.MessageFrame; import org.hyperledger.besu.ethereum.vm.MessageFrame;
@ -200,9 +197,8 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
new PrivateTransactionReceipt( new PrivateTransactionReceipt(
txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); txStatus, result.getLogs(), result.getOutput(), result.getRevertReason());
final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); privateStateUpdater.putTransactionReceipt(
currentBlockHash, commitmentHash, privateTransactionReceipt);
privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt);
maybeUpdateGroupHeadBlockMap(privacyGroupId, currentBlockHash, privateStateUpdater); maybeUpdateGroupHeadBlockMap(privacyGroupId, currentBlockHash, privateStateUpdater);

@ -14,7 +14,6 @@
*/ */
package org.hyperledger.besu.ethereum.privacy; 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 static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH;
import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.Blockchain;
@ -40,7 +39,6 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; 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.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.OperationTracer; import org.hyperledger.besu.ethereum.vm.OperationTracer;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
@ -209,8 +207,6 @@ public class PrivateGroupRehydrationBlockProcessor {
privateStateUpdater, privateStateUpdater,
privateStateStorage); privateStateStorage);
final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo));
final int txStatus = final int txStatus =
result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0; result.getStatus() == PrivateTransactionProcessor.Result.Status.SUCCESSFUL ? 1 : 0;
@ -218,7 +214,8 @@ public class PrivateGroupRehydrationBlockProcessor {
new PrivateTransactionReceipt( new PrivateTransactionReceipt(
txStatus, result.getLogs(), result.getOutput(), result.getRevertReason()); txStatus, result.getLogs(), result.getOutput(), result.getRevertReason());
privateStateUpdater.putTransactionReceipt(currentBlockHash, txHash, privateTransactionReceipt); privateStateUpdater.putTransactionReceipt(
currentBlockHash, commitmentHash, privateTransactionReceipt);
final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap = final PrivacyGroupHeadBlockMap privacyGroupHeadBlockMap =
privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).get(); privateStateStorage.getPrivacyGroupHeadBlockMap(currentBlockHash).get();
if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) { if (!privacyGroupHeadBlockMap.contains(Bytes32.wrap(privacyGroupId), currentBlockHash)) {

@ -50,8 +50,8 @@ public class PrivateStateKeyValueStorage implements PrivateStateStorage {
@Override @Override
public Optional<PrivateTransactionReceipt> getTransactionReceipt( public Optional<PrivateTransactionReceipt> getTransactionReceipt(
final Bytes32 blockHash, final Bytes32 txHash) { final Bytes32 blockHash, final Bytes32 pmtHash) {
final Bytes blockHashTxHash = Bytes.concatenate(blockHash, txHash); final Bytes blockHashTxHash = Bytes.concatenate(blockHash, pmtHash);
return get(blockHashTxHash, TX_RECEIPT_SUFFIX) return get(blockHashTxHash, TX_RECEIPT_SUFFIX)
.map(b -> PrivateTransactionReceipt.readFrom(new BytesValueRLPInput(b, false))); .map(b -> PrivateTransactionReceipt.readFrom(new BytesValueRLPInput(b, false)));
} }

@ -17,8 +17,10 @@ package org.hyperledger.besu.ethereum.mainnet.precompiles.privacy;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.Enclave;
@ -28,6 +30,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; 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.Hash;
import org.hyperledger.besu.ethereum.core.Log; import org.hyperledger.besu.ethereum.core.Log;
import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.MutableWorldState;
@ -37,6 +40,7 @@ import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.mainnet.SpuriousDragonGasCalculator; import org.hyperledger.besu.ethereum.mainnet.SpuriousDragonGasCalculator;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; 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.PrivacyGroupHeadBlockMap;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -59,14 +63,20 @@ import org.junit.rules.TemporaryFolder;
public class PrivacyPrecompiledContractTest { public class PrivacyPrecompiledContractTest {
@Rule public final TemporaryFolder temp = new TemporaryFolder(); @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 String actual = "Test String";
private final Bytes key = Bytes.wrap(actual.getBytes(UTF_8)); 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 MessageFrame messageFrame;
private Blockchain blockchain; private Blockchain blockchain;
private final String DEFAULT_OUTPUT = "0x01"; private PrivacyPrecompiledContract precompiledContract;
final String PAYLOAD_TEST_PRIVACY_GROUP_ID = "8lDVI66RZHIrBsolz6Kn88Rd+WsJ4hUjb4hsh29xW/o=";
private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
final PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class);
private PrivateTransactionProcessor mockPrivateTxProcessor() { private PrivateTransactionProcessor mockPrivateTxProcessor() {
final PrivateTransactionProcessor mockPrivateTransactionProcessor = final PrivateTransactionProcessor mockPrivateTransactionProcessor =
@ -98,7 +108,6 @@ public class PrivacyPrecompiledContractTest {
when(worldStateArchive.getMutable()).thenReturn(mutableWorldState); when(worldStateArchive.getMutable()).thenReturn(mutableWorldState);
when(worldStateArchive.getMutable(any())).thenReturn(Optional.of(mutableWorldState)); when(worldStateArchive.getMutable(any())).thenReturn(Optional.of(mutableWorldState));
final PrivateStateStorage.Updater storageUpdater = mock(PrivateStateStorage.Updater.class);
when(privateStateStorage.getPrivacyGroupHeadBlockMap(any())) when(privateStateStorage.getPrivacyGroupHeadBlockMap(any()))
.thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY)); .thenReturn(Optional.of(PrivacyGroupHeadBlockMap.EMPTY));
when(privateStateStorage.getPrivateBlockMetadata(any(), any())).thenReturn(Optional.empty()); when(privateStateStorage.getPrivateBlockMetadata(any(), any())).thenReturn(Optional.empty());
@ -124,55 +133,62 @@ public class PrivacyPrecompiledContractTest {
when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis)); when(blockchain.getBlockByHash(genesis.getHash())).thenReturn(Optional.of(genesis));
when(messageFrame.getBlockchain()).thenReturn(blockchain); when(messageFrame.getBlockchain()).thenReturn(blockchain);
when(messageFrame.getBlockHeader()).thenReturn(block.getHeader()); when(messageFrame.getBlockHeader()).thenReturn(block.getHeader());
}
@Test precompiledContract =
public void testPayloadFoundInEnclave() {
final Enclave enclave = mock(Enclave.class);
final PrivacyPrecompiledContract contract =
new PrivacyPrecompiledContract( new PrivacyPrecompiledContract(
new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage); new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage);
contract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); precompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor());
}
BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput();
PrivateTransactionDataFixture.privateTransactionBesu().writeTo(bytesValueRLPOutput);
final ReceiveResponse response = @Test
new ReceiveResponse( public void testPayloadFoundInEnclave() {
bytesValueRLPOutput.encoded().toBase64String().getBytes(UTF_8), mockEnclaveWithPrivateTransaction();
PAYLOAD_TEST_PRIVACY_GROUP_ID,
null);
when(enclave.receive(any(String.class))).thenReturn(response);
final Bytes actual = contract.compute(key, messageFrame); final Bytes actual = precompiledContract.compute(key, messageFrame);
assertThat(actual).isEqualTo(Bytes.fromHexString(DEFAULT_OUTPUT)); assertThat(actual).isEqualTo(Bytes.fromHexString(DEFAULT_OUTPUT));
} }
@Test @Test
public void testPayloadNotFoundInEnclave() { 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); 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); assertThat(expected).isEqualTo(Bytes.EMPTY);
} }
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)
public void testEnclaveDown() { public void testEnclaveDown() {
final Enclave enclave = mock(Enclave.class); when(enclave.receive(any(String.class))).thenThrow(new RuntimeException());
final PrivacyPrecompiledContract contract = precompiledContract.compute(key, messageFrame);
new PrivacyPrecompiledContract( }
new SpuriousDragonGasCalculator(), enclave, worldStateArchive, privateStateStorage);
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);
} }
} }

Loading…
Cancel
Save