Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate private transaction before sending to enclave #356

Merged
merged 30 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
70c6100
validate private transactions before sending to enclave
jframe Jan 24, 2020
0269a17
spotless
jframe Jan 24, 2020
c7eeecc
fix PrivDistributeRawTransaction UT
jframe Jan 24, 2020
06f02f5
wip
jframe Jan 28, 2020
08f8fb3
unit tests
jframe Jan 30, 2020
2f3ee18
Merge branch 'master' into validate_private_tx_before_enclave
jframe Jan 30, 2020
d6b7217
small refactoring changes
jframe Jan 30, 2020
e8129df
privateTransactionValidator tests
jframe Jan 30, 2020
f9e321f
renaming some methods
jframe Jan 30, 2020
8aa4f99
private send tx ut
jframe Feb 3, 2020
c0f0b58
cleanup ut
jframe Feb 3, 2020
e2c4c8b
rename uts
jframe Feb 3, 2020
ffb9131
Merge branch 'master' into validate_private_tx_before_enclave
jframe Feb 3, 2020
15d0c9b
add license header
jframe Feb 3, 2020
2137620
fix ut: wip
jframe Feb 3, 2020
265b38a
fix ut
jframe Feb 3, 2020
7e18b95
remove privacy send transaction helper class
jframe Feb 4, 2020
237b109
spotless
jframe Feb 4, 2020
3c481bd
fix unit test
jframe Feb 4, 2020
c137981
besu implementation of eea privacy group generation
jframe Feb 4, 2020
9fe45d5
license header & finals
jframe Feb 4, 2020
9d3fec5
fix bug in privacy group duplicate handling
jframe Feb 4, 2020
c886a63
rename method
jframe Feb 5, 2020
3dc38aa
rename test variables
jframe Feb 6, 2020
7714111
reuse request id in rpcs
jframe Feb 6, 2020
e8a978d
include transaction hash on debug logs
jframe Feb 6, 2020
cbd1c21
move privacyGroup calculation onto PrivateTransaction
jframe Feb 6, 2020
adfdc6c
Merge branch 'master' into validate_private_tx_before_enclave
jframe Feb 7, 2020
3ede57d
rename generateEeaPrivacyGroupId -> calculateEeaPrivacyGroupId
jframe Feb 7, 2020
10534ee
Merge branch 'master' into validate_private_tx_before_enclave
jframe Feb 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
private send tx ut
Signed-off-by: Jason Frame <jasonwframe@gmail.com>
  • Loading branch information
jframe committed Feb 3, 2020
commit 8aa4f991ea46339c76dae9a235bdc62c16e01133
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter.convertEnclaveInvalidReason;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter.convertTransactionInvalidReason;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INVALID_PARAMS;

import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
Expand Down Expand Up @@ -57,23 +58,30 @@ public PrivateTransaction decode(final JsonRpcRequestContext request)
throws ErrorResponseException {
if (request.getRequest().getParamLength() != 1) {
throw new ErrorResponseException(
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.INVALID_PARAMS));
new JsonRpcErrorResponse(request.getRequest().getId(), INVALID_PARAMS));
}
final String rawPrivateTransaction = request.getRequiredParameter(0, String.class);
final PrivateTransaction privateTransaction;
try {
privateTransaction = decodeRawTransaction(rawPrivateTransaction);
} catch (final InvalidJsonRpcRequestException e) {
throw new ErrorResponseException(
new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.DECODE_ERROR));
new JsonRpcErrorResponse(request.getRequest().getId(), DECODE_ERROR));
}
return privateTransaction;
jframe marked this conversation as resolved.
Show resolved Hide resolved
}

public void validate(
final JsonRpcRequestContext request, final PrivateTransaction privateTransaction)
throws ErrorResponseException {
final String privacyGroupId = privacyGroupId(privateTransaction);
final String privacyGroupId;
try {
privacyGroupId = privacyGroupId(privateTransaction);
} catch (IllegalArgumentException e) {
throw new ErrorResponseException(
new JsonRpcErrorResponse(request.getRequest().getId(), INVALID_PARAMS));
}

final String enclaveKey = enclavePublicKeyProvider.getEnclaveKey(request.getUser());
final ValidationResult<TransactionInvalidReason> transactionValidationResult =
privacyController.validatePrivateTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.common.base.MoreObjects;

@JsonPropertyOrder({"jsonrpc", "id", "error"})
public class JsonRpcErrorResponse implements JsonRpcResponse {
Expand Down Expand Up @@ -63,4 +64,9 @@ public boolean equals(final Object o) {
public int hashCode() {
return Objects.hash(id, error);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("id", id).add("error", error).toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.DECODE_ERROR;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.ENCLAVE_ERROR;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError.INVALID_PARAMS;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveIOException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacySendTransaction.ErrorResponseException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.privacy.PrivacyController;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.Restriction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;

import java.math.BigInteger;
import java.util.List;

import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class PrivacySendTransactionTest {
private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=";

@Mock private PrivacyController privacyController;
private PrivacySendTransaction privacySendTransaction;

@Before
public void setup() {
privacySendTransaction =
new PrivacySendTransaction(privacyController, (user) -> ENCLAVE_PUBLIC_KEY);
}

@Test
public void decodingFailsFOrRequestWithMoreThanOneParam() {
final JsonRpcRequestContext requestContext =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eea_sendRawTransaction", new Object[] {"a", "b"}));

assertThatThrownBy(() -> privacySendTransaction.decode(requestContext))
.isInstanceOf(ErrorResponseException.class)
.hasFieldOrPropertyWithValue("response", new JsonRpcErrorResponse(null, INVALID_PARAMS));
}

@Test
public void decodingFailsForRequestWithInvalidPrivateTransaction() {
final JsonRpcRequestContext requestContext =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eea_sendRawTransaction", new Object[] {""}));

assertThatThrownBy(() -> privacySendTransaction.decode(requestContext))
.isInstanceOf(ErrorResponseException.class)
.hasFieldOrPropertyWithValue("response", new JsonRpcErrorResponse(null, DECODE_ERROR));
}

@Test
public void decodesRequestWithValidPrivateTransaction() throws ErrorResponseException {
final SECP256K1.KeyPair keyPair =
SECP256K1.KeyPair.create(
SECP256K1.PrivateKey.create(
new BigInteger(
"8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16)));

final PrivateTransaction.Builder privateTransactionBuilder =
PrivateTransaction.builder()
.nonce(0)
.gasPrice(Wei.of(1))
.gasLimit(21000)
.value(Wei.ZERO)
.payload(Bytes.EMPTY)
.to(Address.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))
.chainId(BigInteger.ONE)
.privateFrom(Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp="))
.privacyGroupId(Bytes.fromBase64String("DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="))
.restriction(Restriction.RESTRICTED);
final PrivateTransaction transaction = privateTransactionBuilder.signAndBuild(keyPair);

final JsonRpcRequestContext requestContext = createSendTransactionRequest(transaction);

final PrivateTransaction responseTransaction = privacySendTransaction.decode(requestContext);
assertThat(responseTransaction).isEqualTo(transaction);
}

@Test
public void validationFailsForRequestWithInvalidPrivacyGroupId() {
final SECP256K1.KeyPair keyPair =
SECP256K1.KeyPair.create(
SECP256K1.PrivateKey.create(
new BigInteger(
"8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16)));

final PrivateTransaction.Builder privateTransactionBuilder =
PrivateTransaction.builder()
.nonce(0)
.gasPrice(Wei.of(1))
.gasLimit(21000)
.value(Wei.ZERO)
.payload(Bytes.EMPTY)
.to(Address.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))
.chainId(BigInteger.ONE)
.privateFrom(Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp="))
.privateFor(
List.of(
Bytes.fromBase64String(
"S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXpwalJ0K0hUdUZCcz0="),
Bytes.fromBase64String("QTFhVnRNeExDVUhtQlZIWG9aenpCZ1BiVy93ajVheER=")))
.restriction(Restriction.RESTRICTED);
final PrivateTransaction transaction = privateTransactionBuilder.signAndBuild(keyPair);

final JsonRpcRequestContext requestContext = createSendTransactionRequest(transaction);

assertThatThrownBy(() -> privacySendTransaction.validate(requestContext, transaction))
.isInstanceOf(ErrorResponseException.class)
.hasFieldOrPropertyWithValue("response", new JsonRpcErrorResponse(null, INVALID_PARAMS));
}

@Test
public void validationFailsForInvalidTransaction() {
final PrivateTransaction transaction = createValidTransaction();

final JsonRpcRequestContext requestContext = createSendTransactionRequest(transaction);

when(privacyController.validatePrivateTransaction(
eq(transaction), anyString(), eq(ENCLAVE_PUBLIC_KEY)))
.thenReturn(ValidationResult.invalid(TransactionInvalidReason.PRIVATE_VALUE_NOT_ZERO));

assertThatThrownBy(() -> privacySendTransaction.validate(requestContext, transaction))
.isInstanceOf(ErrorResponseException.class)
.hasFieldOrPropertyWithValue("response", new JsonRpcErrorResponse(null, INVALID_PARAMS));
}

@Test
public void validationSucceedsForValidTransaction() throws ErrorResponseException {
final PrivateTransaction transaction = createValidTransaction();

final JsonRpcRequestContext requestContext = createSendTransactionRequest(transaction);

when(privacyController.validatePrivateTransaction(
eq(transaction), anyString(), eq(ENCLAVE_PUBLIC_KEY)))
.thenReturn(ValidationResult.valid());

privacySendTransaction.validate(requestContext, transaction);
verify(privacyController)
.validatePrivateTransaction(eq(transaction), anyString(), eq(ENCLAVE_PUBLIC_KEY));
}

@Test
public void sendsTransactionToPrivacyController() throws ErrorResponseException {
final PrivateTransaction privateTransaction = createValidTransaction();
final JsonRpcRequestContext jsonRpcRequest = createSendTransactionRequest(privateTransaction);

privacySendTransaction.sendToEnclave(privateTransaction, jsonRpcRequest);

verify(privacyController).sendTransaction(privateTransaction, ENCLAVE_PUBLIC_KEY);
}

@Test
public void sendingFailsWithErrorResponseExceptionWhenPrivacyControllerReturnsError() {
final PrivateTransaction privateTransaction = createValidTransaction();
final JsonRpcRequestContext jsonRpcRequest = createSendTransactionRequest(privateTransaction);

when(privacyController.sendTransaction(privateTransaction, ENCLAVE_PUBLIC_KEY))
.thenThrow(new EnclaveIOException("enclave failed"));

assertThatThrownBy(
() -> privacySendTransaction.sendToEnclave(privateTransaction, jsonRpcRequest))
.isInstanceOf(ErrorResponseException.class)
.hasFieldOrPropertyWithValue("response", new JsonRpcErrorResponse(null, ENCLAVE_ERROR));

verify(privacyController).sendTransaction(privateTransaction, ENCLAVE_PUBLIC_KEY);
}

private PrivateTransaction createValidTransaction() {
final SECP256K1.KeyPair keyPair =
SECP256K1.KeyPair.create(
SECP256K1.PrivateKey.create(
new BigInteger(
"8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16)));

final PrivateTransaction.Builder privateTransactionBuilder =
PrivateTransaction.builder()
.nonce(0)
.gasPrice(Wei.of(1))
.gasLimit(21000)
.value(Wei.ZERO)
.payload(Bytes.EMPTY)
.to(Address.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"))
.chainId(BigInteger.ONE)
.privateFrom(Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp="))
.privateFor(
List.of(
Bytes.fromBase64String("S28yYlZxRCtuTmxOWUw1RUU3eTNJZE9udmlmdGppaXp="),
Bytes.fromBase64String("QTFhVnRNeExDVUhtQlZIWG9aenpCZ1BiVy93ajVheER=")))
.restriction(Restriction.RESTRICTED);

return privateTransactionBuilder.signAndBuild(keyPair);
}

private JsonRpcRequestContext createSendTransactionRequest(final PrivateTransaction transaction) {
final BytesValueRLPOutput bvrlp = new BytesValueRLPOutput();
transaction.writeTo(bvrlp);
final String rlpEncodedTransaction = bvrlp.encoded().toHexString();

return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eea_sendRawTransaction", new Object[] {rlpEncodedTransaction}));
}
}