[PAN-2647] Validate Private Transaction nonce before submitting to Transaction 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

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Ivaylo Kirilov 6 years ago committed by Eric Kellstrand
parent 7e29b0954b
commit fc4f9917b7
  1. 4
      enclave/src/main/java/tech/pegasys/pantheon/enclave/Enclave.java
  2. 5
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java
  3. 8
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java
  4. 122
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java
  5. 140
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java
  6. 5
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcErrorConverter.java
  7. 56
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java
  8. 2
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java
  9. 61
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java

@ -76,8 +76,8 @@ public class Enclave {
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");
}
}
}

@ -58,6 +58,9 @@ public interface TransactionValidator {
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
}
}

@ -138,11 +138,17 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract {
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();

@ -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;
@ -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;
}
}
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);
}
return createPrivacyMarkerTransactionWithNonce(
sendResponse.getKey(), privateTransaction, nonceSupplier.get());
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) {
@ -95,19 +161,29 @@ public class PrivateTransactionHandler {
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);
}
}

@ -17,19 +17,30 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.crypto.SECP256K1.KeyPair;
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.Hash;
import tech.pegasys.pantheon.ethereum.core.MutableWorldState;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
@ -51,25 +62,6 @@ public class PrivateTransactionHandlerTest {
PrivateTransactionHandler privateTransactionHandler;
PrivateTransactionHandler brokenPrivateTransactionHandler;
private static final PrivateTransaction VALID_PRIVATE_TRANSACTION =
PrivateTransaction.builder()
.nonce(0)
.gasPrice(Wei.of(1000))
.gasLimit(3000000)
.to(Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57"))
.value(Wei.ZERO)
.payload(BytesValue.fromHexString("0x"))
.sender(Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"))
.chainId(BigInteger.valueOf(2018))
.privateFrom(
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)))
.privateFor(
Lists.newArrayList(
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)),
BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))))
.restriction(Restriction.RESTRICTED)
.signAndBuild(KEY_PAIR);
private static final Transaction PUBLIC_TRANSACTION =
Transaction.builder()
.nonce(0)
@ -85,7 +77,9 @@ public class PrivateTransactionHandlerTest {
Enclave mockEnclave() throws IOException {
Enclave mockEnclave = mock(Enclave.class);
SendResponse response = new SendResponse(TRANSACTION_KEY);
ReceiveResponse receiveResponse = new ReceiveResponse(new byte[0], "mock");
when(mockEnclave.send(any(SendRequest.class))).thenReturn(response);
when(mockEnclave.receive(any(ReceiveRequest.class))).thenReturn(receiveResponse);
return mockEnclave;
}
@ -97,27 +91,107 @@ public class PrivateTransactionHandlerTest {
@Before
public void setUp() throws IOException {
PrivateStateStorage privateStateStorage = mock(PrivateStateStorage.class);
Hash mockHash = mock(Hash.class);
when(privateStateStorage.getPrivateAccountState(any(BytesValue.class)))
.thenReturn(Optional.of(mockHash));
WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
Account account = mock(Account.class);
when(account.getNonce()).thenReturn(1L);
MutableWorldState mutableWorldState = mock(MutableWorldState.class);
when(worldStateArchive.getMutable(any(Hash.class))).thenReturn(Optional.of(mutableWorldState));
when(mutableWorldState.get(any(Address.class))).thenReturn(account);
privateTransactionHandler =
new PrivateTransactionHandler(mockEnclave(), Address.DEFAULT_PRIVACY, KEY_PAIR);
new PrivateTransactionHandler(
mockEnclave(),
Address.DEFAULT_PRIVACY,
KEY_PAIR,
privateStateStorage,
worldStateArchive);
brokenPrivateTransactionHandler =
new PrivateTransactionHandler(brokenMockEnclave(), Address.DEFAULT_PRIVACY, KEY_PAIR);
new PrivateTransactionHandler(
brokenMockEnclave(),
Address.DEFAULT_PRIVACY,
KEY_PAIR,
privateStateStorage,
worldStateArchive);
}
@Test
public void validTransactionThroughHandler() throws IOException {
final Transaction transactionResponse =
privateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION, () -> 0L);
assertThat(transactionResponse.contractAddress())
.isEqualTo(PUBLIC_TRANSACTION.contractAddress());
assertThat(transactionResponse.getPayload()).isEqualTo(PUBLIC_TRANSACTION.getPayload());
assertThat(transactionResponse.getNonce()).isEqualTo(PUBLIC_TRANSACTION.getNonce());
assertThat(transactionResponse.getSender()).isEqualTo(PUBLIC_TRANSACTION.getSender());
assertThat(transactionResponse.getValue()).isEqualTo(PUBLIC_TRANSACTION.getValue());
public void validTransactionThroughHandler() throws Exception {
final PrivateTransaction transaction = buildPrivateTransaction(1);
final String enclaveKey = privateTransactionHandler.sendToOrion(transaction);
final String privacyGroupId =
privateTransactionHandler.getPrivacyGroup(enclaveKey, transaction.getPrivateFrom());
final ValidationResult<TransactionInvalidReason> validationResult =
privateTransactionHandler.validatePrivateTransaction(transaction, privacyGroupId);
final Transaction markerTransaction =
privateTransactionHandler.createPrivacyMarkerTransaction(enclaveKey, transaction, 0L);
assertThat(validationResult).isEqualTo(ValidationResult.valid());
assertThat(markerTransaction.contractAddress()).isEqualTo(PUBLIC_TRANSACTION.contractAddress());
assertThat(markerTransaction.getPayload()).isEqualTo(PUBLIC_TRANSACTION.getPayload());
assertThat(markerTransaction.getNonce()).isEqualTo(PUBLIC_TRANSACTION.getNonce());
assertThat(markerTransaction.getSender()).isEqualTo(PUBLIC_TRANSACTION.getSender());
assertThat(markerTransaction.getValue()).isEqualTo(PUBLIC_TRANSACTION.getValue());
}
@Test(expected = IOException.class)
public void enclaveIsDownWhileHandling() throws IOException {
brokenPrivateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION, () -> 0L);
public void enclaveIsDownWhileHandling() throws Exception {
brokenPrivateTransactionHandler.sendToOrion(buildPrivateTransaction());
}
@Test
public void nonceTooLowError() throws Exception {
final PrivateTransaction transaction = buildPrivateTransaction(0);
final String enclaveKey = privateTransactionHandler.sendToOrion(transaction);
final String privacyGroupId =
privateTransactionHandler.getPrivacyGroup(enclaveKey, transaction.getPrivateFrom());
final ValidationResult<TransactionInvalidReason> validationResult =
privateTransactionHandler.validatePrivateTransaction(transaction, privacyGroupId);
assertThat(validationResult).isEqualTo(ValidationResult.invalid(PRIVATE_NONCE_TOO_LOW));
}
@Test
public void incorrectNonceError() throws Exception {
final PrivateTransaction transaction = buildPrivateTransaction(2);
final String enclaveKey = privateTransactionHandler.sendToOrion(transaction);
final String privacyGroupId =
privateTransactionHandler.getPrivacyGroup(enclaveKey, transaction.getPrivateFrom());
final ValidationResult<TransactionInvalidReason> validationResult =
privateTransactionHandler.validatePrivateTransaction(transaction, privacyGroupId);
assertThat(validationResult).isEqualTo(ValidationResult.invalid(INCORRECT_PRIVATE_NONCE));
}
private static PrivateTransaction buildPrivateTransaction() {
return buildPrivateTransaction(0);
}
private static PrivateTransaction buildPrivateTransaction(final long nonce) {
return PrivateTransaction.builder()
.nonce(nonce)
.gasPrice(Wei.of(1000))
.gasLimit(3000000)
.to(Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57"))
.value(Wei.ZERO)
.payload(BytesValue.fromHexString("0x"))
.sender(Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"))
.chainId(BigInteger.valueOf(2018))
.privateFrom(
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)))
.privateFor(
Lists.newArrayList(
BytesValue.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=".getBytes(UTF_8)),
BytesValue.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=".getBytes(UTF_8))))
.restriction(Restriction.RESTRICTED)
.signAndBuild(KEY_PAIR);
}
}

@ -34,8 +34,13 @@ public class JsonRpcErrorConverter {
return JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT;
case TX_SENDER_NOT_AUTHORIZED:
return JsonRpcError.TX_SENDER_NOT_AUTHORIZED;
// Private Transaction Invalid Reasons
case CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE:
return JsonRpcError.CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE;
case PRIVATE_NONCE_TOO_LOW:
return JsonRpcError.PRIVATE_NONCE_TOO_LOW;
case INCORRECT_PRIVATE_NONCE:
return JsonRpcError.INCORRECT_PRIVATE_NONCE;
default:
return JsonRpcError.INVALID_PARAMS;

@ -27,8 +27,6 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction;
import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler;
import tech.pegasys.pantheon.ethereum.privacy.Restriction;
@ -89,34 +87,42 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
request.getId(), JsonRpcError.UNIMPLEMENTED_PRIVATE_TRANSACTION_TYPE);
}
final Transaction transaction;
final String enclaveKey;
try {
transaction = handlePrivateTransaction(privateTransaction);
} catch (final InvalidJsonRpcRequestException e) {
enclaveKey = privateTransactionHandler.sendToOrion(privateTransaction);
} catch (final IOException e) {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.ENCLAVE_ERROR);
}
final ValidationResult<TransactionInvalidReason> validationResult =
transactionPool.addLocalTransaction(transaction);
return validationResult.either(
() -> new JsonRpcSuccessResponse(request.getId(), transaction.hash().toString()),
errorReason ->
new JsonRpcErrorResponse(
request.getId(), convertTransactionInvalidReason(errorReason)));
}
protected long getNonce(final Address address) {
return blockchain.getTransactionCount(address, blockchain.headBlockNumber());
}
private Transaction handlePrivateTransaction(final PrivateTransaction privateTransaction)
throws InvalidJsonRpcRequestException {
final String privacyGroupId;
try {
return privateTransactionHandler.handle(
privateTransaction, () -> getNonce(privateTransaction.getSender()));
privacyGroupId =
privateTransactionHandler.getPrivacyGroup(
enclaveKey, privateTransaction.getPrivateFrom());
} catch (final IOException e) {
throw new InvalidJsonRpcRequestException("Unable to handle private transaction", e);
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.ENCLAVE_ERROR);
}
return privateTransactionHandler
.validatePrivateTransaction(privateTransaction, privacyGroupId)
.either(
() -> {
final Transaction privacyMarkerTransaction =
privateTransactionHandler.createPrivacyMarkerTransaction(
enclaveKey, privateTransaction, getNonce(privateTransaction.getSender()));
return transactionPool
.addLocalTransaction(privacyMarkerTransaction)
.either(
() ->
new JsonRpcSuccessResponse(
request.getId(), privacyMarkerTransaction.hash().toString()),
errorReason ->
new JsonRpcErrorResponse(
request.getId(), convertTransactionInvalidReason(errorReason)));
},
(errorReason) ->
new JsonRpcErrorResponse(
request.getId(), convertTransactionInvalidReason(errorReason)));
}
private PrivateTransaction decodeRawTransaction(final String hash)
@ -128,4 +134,8 @@ public class EeaSendRawTransaction implements JsonRpcMethod {
throw new InvalidJsonRpcRequestException("Invalid raw private transaction hex", e);
}
}
protected long getNonce(final Address address) {
return blockchain.getTransactionCount(address, blockchain.headBlockNumber());
}
}

@ -91,6 +91,8 @@ public enum JsonRpcError {
// Private transaction errors
ENCLAVE_ERROR(-50100, "Error communicating with enclave"),
PRIVATE_NONCE_TOO_LOW(-50100, "Private transaction nonce too low"),
INCORRECT_PRIVATE_NONCE(-50100, "Private transaction nonce is incorrect"),
UNIMPLEMENTED_PRIVATE_TRANSACTION_TYPE(-50100, "Unimplemented private transaction type"),
PRIVATE_TRANSACTION_RECEIPT_ERROR(-50100, "Error generating the private transaction receipt"),
VALUE_NOT_ZERO(-50100, "We cannot transfer ether in private transaction yet."),

@ -39,7 +39,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
@ -47,7 +46,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@SuppressWarnings("unchecked")
@RunWith(MockitoJUnitRunner.class)
public class EeaSendRawTransactionTest {
@ -89,6 +87,9 @@ public class EeaSendRawTransactionTest {
Address.wrap(BytesValue.fromHexString("0x8411b12666f68ef74cace3615c9d5a377729d03f")),
Optional.empty());
final String MOCK_ORION_KEY = "";
final String MOCK_PRIVACY_GROUP = "";
@Mock private TransactionPool transactionPool;
@Mock private JsonRpcParameter parameter;
@ -177,10 +178,17 @@ public class EeaSendRawTransactionTest {
}
@Test
public void validTransactionIsSentToTransactionPool() throws IOException {
public void validTransactionIsSentToTransactionPool() throws Exception {
when(parameter.required(any(Object[].class), anyInt(), any()))
.thenReturn(VALID_PRIVATE_TRANSACTION_RLP);
when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class)))
when(privateTxHandler.sendToOrion(any(PrivateTransaction.class))).thenReturn(MOCK_ORION_KEY);
when(privateTxHandler.getPrivacyGroup(any(String.class), any(BytesValue.class)))
.thenReturn(MOCK_PRIVACY_GROUP);
when(privateTxHandler.validatePrivateTransaction(
any(PrivateTransaction.class), any(String.class)))
.thenReturn(ValidationResult.valid());
when(privateTxHandler.createPrivacyMarkerTransaction(
any(String.class), any(PrivateTransaction.class), any(Long.class)))
.thenReturn(PUBLIC_TRANSACTION);
when(transactionPool.addLocalTransaction(any(Transaction.class)))
.thenReturn(ValidationResult.valid());
@ -196,15 +204,21 @@ public class EeaSendRawTransactionTest {
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(privateTxHandler).handle(any(PrivateTransaction.class), any(Supplier.class));
verify(privateTxHandler).sendToOrion(any(PrivateTransaction.class));
verify(privateTxHandler).getPrivacyGroup(any(String.class), any(BytesValue.class));
verify(privateTxHandler)
.validatePrivateTransaction(any(PrivateTransaction.class), any(String.class));
verify(privateTxHandler)
.createPrivacyMarkerTransaction(
any(String.class), any(PrivateTransaction.class), any(Long.class));
verify(transactionPool).addLocalTransaction(any(Transaction.class));
}
@Test
public void invalidTransactionIsSentToTransactionPool() throws IOException {
public void invalidTransactionIsSentToTransactionPool() throws Exception {
when(parameter.required(any(Object[].class), anyInt(), any()))
.thenReturn(VALID_PRIVATE_TRANSACTION_RLP);
when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class)))
when(privateTxHandler.sendToOrion(any(PrivateTransaction.class)))
.thenThrow(new IOException("enclave failed to execute"));
final JsonRpcRequest request =
@ -220,55 +234,62 @@ public class EeaSendRawTransactionTest {
}
@Test
public void transactionWithNonceBelowAccountNonceIsRejected() throws IOException {
public void transactionWithNonceBelowAccountNonceIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.NONCE_TOO_LOW, JsonRpcError.NONCE_TOO_LOW);
}
@Test
public void transactionWithNonceAboveAccountNonceIsRejected() throws IOException {
public void transactionWithNonceAboveAccountNonceIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INCORRECT_NONCE, JsonRpcError.INCORRECT_NONCE);
}
@Test
public void transactionWithInvalidSignatureIsRejected() throws IOException {
public void transactionWithInvalidSignatureIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INVALID_SIGNATURE, JsonRpcError.INVALID_TRANSACTION_SIGNATURE);
}
@Test
public void transactionWithIntrinsicGasExceedingGasLimitIsRejected() throws IOException {
public void transactionWithIntrinsicGasExceedingGasLimitIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT,
JsonRpcError.INTRINSIC_GAS_EXCEEDS_LIMIT);
}
@Test
public void transactionWithUpfrontGasExceedingAccountBalanceIsRejected() throws IOException {
public void transactionWithUpfrontGasExceedingAccountBalanceIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);
}
@Test
public void transactionWithGasLimitExceedingBlockGasLimitIsRejected() throws IOException {
public void transactionWithGasLimitExceedingBlockGasLimitIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT, JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT);
}
@Test
public void transactionWithNotWhitelistedSenderAccountIsRejected() throws IOException {
public void transactionWithNotWhitelistedSenderAccountIsRejected() throws Exception {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, JsonRpcError.TX_SENDER_NOT_AUTHORIZED);
}
private void verifyErrorForInvalidTransaction(
final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError)
throws IOException {
throws Exception {
when(parameter.required(any(Object[].class), anyInt(), any()))
.thenReturn(VALID_PRIVATE_TRANSACTION_RLP);
when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class)))
when(privateTxHandler.sendToOrion(any(PrivateTransaction.class))).thenReturn(MOCK_ORION_KEY);
when(privateTxHandler.getPrivacyGroup(any(String.class), any(BytesValue.class)))
.thenReturn(MOCK_PRIVACY_GROUP);
when(privateTxHandler.validatePrivateTransaction(
any(PrivateTransaction.class), any(String.class)))
.thenReturn(ValidationResult.valid());
when(privateTxHandler.createPrivacyMarkerTransaction(
any(String.class), any(PrivateTransaction.class), any(Long.class)))
.thenReturn(PUBLIC_TRANSACTION);
when(transactionPool.addLocalTransaction(any(Transaction.class)))
.thenReturn(ValidationResult.invalid(transactionInvalidReason));
@ -283,7 +304,13 @@ public class EeaSendRawTransactionTest {
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(privateTxHandler).handle(any(PrivateTransaction.class), any(Supplier.class));
verify(privateTxHandler).sendToOrion(any(PrivateTransaction.class));
verify(privateTxHandler).getPrivacyGroup(any(String.class), any(BytesValue.class));
verify(privateTxHandler)
.validatePrivateTransaction(any(PrivateTransaction.class), any(String.class));
verify(privateTxHandler)
.createPrivacyMarkerTransaction(
any(String.class), any(PrivateTransaction.class), any(Long.class));
verify(transactionPool).addLocalTransaction(any(Transaction.class));
}

Loading…
Cancel
Save