Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
[PAN-2647] Validate Private Transaction nonce before submitting to Tr…
Browse files Browse the repository at this point in the history
…ansaction Pool (#1449)

* Validate private transaction nonce before submitting to Transaction Pool
* Update tests for Incorrect Nonce and Nonce Too Low exceptions
* Differentiate Incorrect Nonce and Nonce Too Low error messages
* Fixed flaky tests
* Change log level from Info to Debug
  • Loading branch information
iikirilov authored and ekellstrand committed May 23, 2019
1 parent bb75876 commit 6df19d2
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ private <T> T executePost(final Request request, final Class<T> responseType) th
try (Response response = client.newCall(request).execute()) {
return objectMapper.readValue(response.body().string(), responseType);
} catch (IOException e) {
LOG.error("Enclave failed to execute ", request);
throw new IOException("Enclave failed to execute post", e);
LOG.error("Enclave failed to execute {}", request, e);
throw new IOException("Enclave failed to execute post");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ enum TransactionInvalidReason {
EXCEEDS_BLOCK_GAS_LIMIT,
TX_SENDER_NOT_AUTHORIZED,
CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE,
PRIVATE_TRANSACTION_FAILED
// Private Transaction Invalid Reasons
PRIVATE_TRANSACTION_FAILED,
PRIVATE_NONCE_TOO_LOW,
INCORRECT_PRIVATE_NONCE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,17 @@ public BytesValue compute(final BytesValue input, final MessageFrame messageFram
privacyGroupId);

if (result.isInvalid() || !result.isSuccessful()) {
LOG.error("Unable to process the private transaction: {}", result.getValidationResult());
LOG.error(
"Failed to process the private transaction: {}",
result.getValidationResult().getErrorMessage());
return BytesValue.EMPTY;
}

if (messageFrame.isPersistingState()) {
LOG.trace(
"Persisting private state {} for privacyGroup {}",
disposablePrivateState.rootHash(),
privacyGroupId);
privateWorldStateUpdater.commit();
disposablePrivateState.persist();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@
*/
package tech.pegasys.pantheon.ethereum.privacy;

import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.INCORRECT_PRIVATE_NONCE;
import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.PRIVATE_NONCE_TOO_LOW;

import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.enclave.Enclave;
import tech.pegasys.pantheon.enclave.types.ReceiveRequest;
import tech.pegasys.pantheon.enclave.types.ReceiveResponse;
import tech.pegasys.pantheon.enclave.types.SendRequest;
import tech.pegasys.pantheon.enclave.types.SendResponse;
import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues;

import java.io.IOException;
import java.util.Base64;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import com.google.common.base.Charsets;
Expand All @@ -40,39 +48,97 @@ public class PrivateTransactionHandler {
private final Enclave enclave;
private final Address privacyPrecompileAddress;
private final SECP256K1.KeyPair nodeKeyPair;
private final PrivateStateStorage privateStateStorage;
private final WorldStateArchive privateWorldStateArchive;

public PrivateTransactionHandler(final PrivacyParameters privacyParameters) {
this(
new Enclave(privacyParameters.getEnclaveUri()),
Address.privacyPrecompiled(privacyParameters.getPrivacyAddress()),
privacyParameters.getSigningKeyPair());
privacyParameters.getSigningKeyPair(),
privacyParameters.getPrivateStateStorage(),
privacyParameters.getPrivateWorldStateArchive());
}

public PrivateTransactionHandler(
final Enclave enclave,
final Address privacyPrecompileAddress,
final SECP256K1.KeyPair nodeKeyPair) {
final SECP256K1.KeyPair nodeKeyPair,
final PrivateStateStorage privateStateStorage,
final WorldStateArchive privateWorldStateArchive) {
this.enclave = enclave;
this.privacyPrecompileAddress = privacyPrecompileAddress;
this.nodeKeyPair = nodeKeyPair;
this.privateStateStorage = privateStateStorage;
this.privateWorldStateArchive = privateWorldStateArchive;
}

public Transaction handle(
final PrivateTransaction privateTransaction, final Supplier<Long> nonceSupplier)
throws IOException {
LOG.trace("Handling private transaction {}", privateTransaction.toString());
public String sendToOrion(final PrivateTransaction privateTransaction) throws IOException {
final SendRequest sendRequest = createSendRequest(privateTransaction);
final SendResponse sendResponse;

try {
LOG.trace("Storing private transaction in enclave");
sendResponse = enclave.send(sendRequest);
return sendResponse.getKey();
} catch (IOException e) {
LOG.error("Failed to store private transaction in enclave", e);
throw e;
}
}

return createPrivacyMarkerTransactionWithNonce(
sendResponse.getKey(), privateTransaction, nonceSupplier.get());
public String getPrivacyGroup(final String key, final BytesValue from) throws IOException {
final ReceiveRequest receiveRequest = new ReceiveRequest(key, BytesValues.asString(from));
LOG.debug("Getting privacy group for {}", BytesValues.asString(from));
final ReceiveResponse receiveResponse;
try {
receiveResponse = enclave.receive(receiveRequest);
return BytesValue.wrap(receiveResponse.getPrivacyGroupId().getBytes(Charsets.UTF_8))
.toString();
} catch (IOException e) {
LOG.error("Failed to retrieve private transaction in enclave", e);
throw e;
}
}

public Transaction createPrivacyMarkerTransaction(
final String transactionEnclaveKey,
final PrivateTransaction privateTransaction,
final Long nonce) {

return Transaction.builder()
.nonce(nonce)
.gasPrice(privateTransaction.getGasPrice())
.gasLimit(privateTransaction.getGasLimit())
.to(privacyPrecompileAddress)
.value(privateTransaction.getValue())
.payload(BytesValue.wrap(transactionEnclaveKey.getBytes(Charsets.UTF_8)))
.sender(privateTransaction.getSender())
.signAndBuild(nodeKeyPair);
}

public ValidationResult<TransactionInvalidReason> validatePrivateTransaction(
final PrivateTransaction privateTransaction, final String privacyGroupId) {
final long actualNonce = privateTransaction.getNonce();
final long expectedNonce = getSenderNonce(privateTransaction, privacyGroupId);
LOG.debug("Validating actual nonce {} with expected nonce {}", actualNonce, expectedNonce);
if (expectedNonce > actualNonce) {
return ValidationResult.invalid(
PRIVATE_NONCE_TOO_LOW,
String.format(
"private transaction nonce %s does not match sender account nonce %s.",
actualNonce, expectedNonce));
}

if (expectedNonce != actualNonce) {
return ValidationResult.invalid(
INCORRECT_PRIVATE_NONCE,
String.format(
"private transaction nonce %s does not match sender account nonce %s.",
actualNonce, expectedNonce));
}

return ValidationResult.valid();
}

private SendRequest createSendRequest(final PrivateTransaction privateTransaction) {
Expand All @@ -95,19 +161,29 @@ private SendRequest createSendRequest(final PrivateTransaction privateTransactio
privateFor);
}

private Transaction createPrivacyMarkerTransactionWithNonce(
final String transactionEnclaveKey,
final PrivateTransaction privateTransaction,
final Long nonce) {

return Transaction.builder()
.nonce(nonce)
.gasPrice(privateTransaction.getGasPrice())
.gasLimit(privateTransaction.getGasLimit())
.to(privacyPrecompileAddress)
.value(privateTransaction.getValue())
.payload(BytesValue.wrap(transactionEnclaveKey.getBytes(Charsets.UTF_8)))
.sender(privateTransaction.getSender())
.signAndBuild(nodeKeyPair);
private long getSenderNonce(
final PrivateTransaction privateTransaction, final String privacyGroupId) {
return privateStateStorage
.getPrivateAccountState(BytesValue.fromHexString(privacyGroupId))
.map(
lastRootHash ->
privateWorldStateArchive
.getMutable(lastRootHash)
.map(
worldState -> {
final Account maybePrivateSender =
worldState.get(privateTransaction.getSender());

if (maybePrivateSender != null) {
return maybePrivateSender.getNonce();
}
// account has not interacted in this private state
return Account.DEFAULT_NONCE;
})
// private state does not exist
.orElse(Account.DEFAULT_NONCE))
.orElse(
// private state does not exist
Account.DEFAULT_NONCE);
}
}
Loading

0 comments on commit 6df19d2

Please sign in to comment.