Repository: consensys/tessera
Issue: Cannot send raw transactions with no other private participants

When sending a raw transaction with no recipients, the transaction failed to be retrieved. This is caused by the fact we have no recipient public key to decrypt with.

For regular transactions, we add ourselves as a recipient for exactly this case, and the same should be done for raw transactions.

Changed files:
tessera-jaxrs/thirdparty-jaxrs/src/main/java/com/quorum/tessera/thirdparty/RawTransactionResource.java
tessera-core/src/main/java/com/quorum/tessera/transaction/TransactionManagerImpl.java
tessera-core/src/test/java/com/quorum/tessera/transaction/TransactionManagerTest.java
tessera-jaxrs/thirdparty-jaxrs/src/test/java/com/quorum/tessera/thirdparty/RawTransactionResourceTest.java

diff --git a/tessera-core/src/main/java/com/quorum/tessera/transaction/TransactionManagerImpl.java b/tessera-core/src/main/java/com/quorum/tessera/transaction/TransactionManagerImpl.java
index 73008b79b..ca2eb294f 100644
--- a/tessera-core/src/main/java/com/quorum/tessera/transaction/TransactionManagerImpl.java
+++ b/tessera-core/src/main/java/com/quorum/tessera/transaction/TransactionManagerImpl.java
@@ -72,7 +72,7 @@ public class TransactionManagerImpl implements TransactionManager {
     }
 
     /*
-    Only sue for tests
+    Only use for tests
     */
     public TransactionManagerImpl(
             Base64Decoder base64Decoder,
@@ -149,7 +149,7 @@ public class TransactionManagerImpl implements TransactionManager {
 
     @Override
     @Transactional
-    public SendResponse sendSignedTransaction(SendSignedRequest sendRequest) {
+    public SendResponse sendSignedTransaction(final SendSignedRequest sendRequest) {
 
         final byte[][] recipients =
                 Stream.of(sendRequest)
@@ -162,7 +162,7 @@ public class TransactionManagerImpl implements TransactionManager {
 
         recipientList.addAll(enclave.getForwardingKeys());
 
-        MessageHash messageHash = new MessageHash(sendRequest.getHash());
+        final MessageHash messageHash = new MessageHash(sendRequest.getHash());
 
         EncryptedRawTransaction encryptedRawTransaction =
                 encryptedRawTransactionDAO
@@ -172,6 +172,8 @@ public class TransactionManagerImpl implements TransactionManager {
                                         new TransactionNotFoundException(
                                                 "Raw Transaction with hash " + messageHash + " was not found"));
 
+        recipientList.add(PublicKey.from(encryptedRawTransaction.getSender()));
+
         final EncodedPayload payload =
                 enclave.encryptPayload(encryptedRawTransaction.toRawTransaction(), recipientList);
 
diff --git a/tessera-core/src/test/java/com/quorum/tessera/transaction/TransactionManagerTest.java b/tessera-core/src/test/java/com/quorum/tessera/transaction/TransactionManagerTest.java
index 82708bf9c..4dab10e18 100644
--- a/tessera-core/src/test/java/com/quorum/tessera/transaction/TransactionManagerTest.java
+++ b/tessera-core/src/test/java/com/quorum/tessera/transaction/TransactionManagerTest.java
@@ -151,10 +151,12 @@ public class TransactionManagerTest {
 
         verify(enclave).encryptPayload(any(RawTransaction.class), any());
         verify(payloadEncoder).encode(payload);
-        verify(payloadEncoder).forRecipient(any(EncodedPayload.class), any(PublicKey.class));
+        verify(payloadEncoder).forRecipient(any(EncodedPayload.class), eq(PublicKey.from("SENDER".getBytes())));
+        verify(payloadEncoder).forRecipient(any(EncodedPayload.class), eq(PublicKey.from("RECEIVER".getBytes())));
         verify(encryptedTransactionDAO).save(any(EncryptedTransaction.class));
         verify(encryptedRawTransactionDAO).retrieveByHash(any(MessageHash.class));
-        verify(partyInfoService).publishPayload(any(EncodedPayload.class), any(PublicKey.class));
+        verify(partyInfoService).publishPayload(any(EncodedPayload.class), eq(PublicKey.from("SENDER".getBytes())));
+        verify(partyInfoService).publishPayload(any(EncodedPayload.class), eq(PublicKey.from("RECEIVER".getBytes())));
         verify(enclave).getForwardingKeys();
     }
 
diff --git a/tessera-jaxrs/thirdparty-jaxrs/src/main/java/com/quorum/tessera/thirdparty/RawTransactionResource.java b/tessera-jaxrs/thirdparty-jaxrs/src/main/java/com/quorum/tessera/thirdparty/RawTransactionResource.java
index b8a86a7cb..c7032b975 100644
--- a/tessera-jaxrs/thirdparty-jaxrs/src/main/java/com/quorum/tessera/thirdparty/RawTransactionResource.java
+++ b/tessera-jaxrs/thirdparty-jaxrs/src/main/java/com/quorum/tessera/thirdparty/RawTransactionResource.java
@@ -1,6 +1,7 @@
 package com.quorum.tessera.thirdparty;
 
-import com.quorum.tessera.api.model.*;
+import com.quorum.tessera.api.model.StoreRawRequest;
+import com.quorum.tessera.api.model.StoreRawResponse;
 import com.quorum.tessera.core.api.ServiceFactory;
 import com.quorum.tessera.transaction.TransactionManager;
 import io.swagger.annotations.ApiOperation;
@@ -12,12 +13,14 @@ import org.slf4j.LoggerFactory;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
-import javax.ws.rs.*;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
 import java.util.Objects;
 
-import static javax.ws.rs.core.MediaType.*;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 /** Provides endpoints for dealing with raw transactions */
 @Path("/")
@@ -25,13 +28,15 @@ public class RawTransactionResource {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(RawTransactionResource.class);
 
+    public static final String ENDPOINT_STORE_RAW = "storeraw";
+
     private final TransactionManager delegate;
 
     public RawTransactionResource() {
         this(ServiceFactory.create().transactionManager());
     }
 
-    public RawTransactionResource(TransactionManager delegate) {
+    public RawTransactionResource(final TransactionManager delegate) {
         this.delegate = Objects.requireNonNull(delegate);
     }
 
@@ -41,15 +46,13 @@ public class RawTransactionResource {
         @ApiResponse(code = 400, message = "For unknown sender")
     })
     @POST
-    @Path("storeraw")
+    @Path(ENDPOINT_STORE_RAW)
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     public Response store(
-            @ApiParam(name = "storeRawRequest", required = true) @NotNull @Valid
-                    final StoreRawRequest storeRawRequest) {
-
-        final StoreRawResponse response = delegate.store(storeRawRequest);
+            @ApiParam(name = "storeRawRequest", required = true) @NotNull @Valid final StoreRawRequest request) {
+        final StoreRawResponse response = delegate.store(request);
 
-        return Response.status(Status.OK).type(APPLICATION_JSON).entity(response).build();
+        return Response.ok().type(APPLICATION_JSON).entity(response).build();
     }
 }
diff --git a/tessera-jaxrs/thirdparty-jaxrs/src/test/java/com/quorum/tessera/thirdparty/RawTransactionResourceTest.java b/tessera-jaxrs/thirdparty-jaxrs/src/test/java/com/quorum/tessera/thirdparty/RawTransactionResourceTest.java
index 4ff1488f3..47a0b645c 100644
--- a/tessera-jaxrs/thirdparty-jaxrs/src/test/java/com/quorum/tessera/thirdparty/RawTransactionResourceTest.java
+++ b/tessera-jaxrs/thirdparty-jaxrs/src/test/java/com/quorum/tessera/thirdparty/RawTransactionResourceTest.java
@@ -1,6 +1,5 @@
 package com.quorum.tessera.thirdparty;
 
-import com.quorum.tessera.thirdparty.RawTransactionResource;
 import com.quorum.tessera.api.model.StoreRawRequest;
 import com.quorum.tessera.transaction.TransactionManager;
 import org.junit.After;
@@ -10,7 +9,6 @@ import org.junit.Test;
 import javax.ws.rs.core.Response;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.*;
 
 public class RawTransactionResourceTest {
@@ -21,8 +19,9 @@ public class RawTransactionResourceTest {
 
     @Before
     public void onSetup() {
-        transactionManager = mock(TransactionManager.class);
-        transactionResource = new RawTransactionResource(transactionManager);
+        this.transactionManager = mock(TransactionManager.class);
+
+        this.transactionResource = new RawTransactionResource(transactionManager);
     }
 
     @After
@@ -32,12 +31,12 @@ public class RawTransactionResourceTest {
 
     @Test
     public void store() {
-        StoreRawRequest storeRawRequest = new StoreRawRequest();
+        final StoreRawRequest storeRawRequest = new StoreRawRequest();
         storeRawRequest.setPayload("PAYLOAD".getBytes());
 
-        Response result = transactionResource.store(storeRawRequest);
-        assertThat(result.getStatus()).isEqualTo(200);
+        final Response result = transactionResource.store(storeRawRequest);
 
-        verify(transactionManager).store(same(storeRawRequest));
+        assertThat(result.getStatus()).isEqualTo(200);
+        verify(transactionManager).store(storeRawRequest);
     }
 }