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 1bd4c1420f..84df379ef6 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 @@ -1,11 +1,7 @@ package com.quorum.tessera.transaction; import com.quorum.tessera.api.model.*; -import com.quorum.tessera.enclave.Enclave; -import com.quorum.tessera.enclave.EnclaveException; -import com.quorum.tessera.enclave.EncodedPayload; -import com.quorum.tessera.enclave.PayloadEncoder; -import com.quorum.tessera.enclave.RawTransaction; +import com.quorum.tessera.enclave.*; import com.quorum.tessera.enclave.model.MessageHash; import com.quorum.tessera.enclave.model.MessageHashFactory; import com.quorum.tessera.encryption.PublicKey; @@ -183,6 +179,9 @@ public ResendResponse resend(ResendRequest request) { final boolean isSender = Objects.equals(payload.getSenderKey(), recipientPublicKey); return isRecipient || isSender; }).forEach(payload -> { + + final EncodedPayload prunedPayload; + if (Objects.equals(payload.getSenderKey(), recipientPublicKey)) { final PublicKey decryptedKey = searchForRecipientKey(payload).orElseThrow( () -> { @@ -192,10 +191,15 @@ public ResendResponse resend(ResendRequest request) { } ); payload.getRecipientKeys().add(decryptedKey); + + // This payload does not need to be pruned as it was not sent by this node and so does not contain any other node's data + prunedPayload = payload; + } else { + prunedPayload = payloadEncoder.forRecipient(payload, recipientPublicKey); } try { - payloadPublisher.publishPayload(payload, recipientPublicKey); + payloadPublisher.publishPayload(prunedPayload, recipientPublicKey); } catch (PublishPayloadException ex) { LOGGER.warn("Unable to publish payload to recipient {} during resend", recipientPublicKey.encodeToBase64()); } @@ -276,7 +280,7 @@ public ReceiveResponse receive(ReceiveRequest request) { final MessageHash hash = new MessageHash(key); LOGGER.info("Lookup transaction {}",hash); - + final EncryptedTransaction encryptedTransaction = encryptedTransactionDAO .retrieveByHash(hash) .orElseThrow(() -> new TransactionNotFoundException("Message with hash " + hash + " was not found")); @@ -327,7 +331,7 @@ public StoreRawResponse store(StoreRawRequest storeRequest) { return new StoreRawResponse(encryptedRawTransaction.getHash().getHashBytes()); } - - + + } 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 081876c9dc..0ab686898c 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 @@ -234,6 +234,7 @@ public void resendAllWhereRequestedIsSenderAndRecipientExists() { verify(payloadPublisher).publishPayload(any(EncodedPayload.class), eq(senderKey)); verify(enclave).getPublicKeys(); verify(enclave).unencryptTransaction(payload, recipientKey); + verify(payloadEncoder, never()).forRecipient(any(EncodedPayload.class), any(PublicKey.class)); } @Test @@ -272,6 +273,7 @@ public void resendAllWhereRequestedIsRecipient() { when(encryptedTransactionDAO.retrieveAllTransactions()).thenReturn(singletonList(tx)); when(payloadEncoder.decode(any(byte[].class))).thenReturn(payload); + when(payloadEncoder.forRecipient(payload, recipientKey)).thenReturn(payload); final ResendRequest resendRequest = new ResendRequest(); resendRequest.setPublicKey(recipientKey.encodeToBase64()); @@ -283,9 +285,94 @@ public void resendAllWhereRequestedIsRecipient() { verify(encryptedTransactionDAO).retrieveAllTransactions(); verify(payloadEncoder).decode(encodedData); + verify(payloadEncoder).forRecipient(payload, recipientKey); verify(payloadPublisher).publishPayload(any(EncodedPayload.class), eq(recipientKey)); } + @Test + public void resendAllWhereRequestedIsRecipientThenPublishedPayloadDoesNotContainDataForOtherRecipients() { + + final byte[] encodedData = "transaction".getBytes(); + final EncryptedTransaction tx = new EncryptedTransaction(mock(MessageHash.class), encodedData); + final EncodedPayload payload = mock(EncodedPayload.class); + + final List recipients = new ArrayList<>(); + final PublicKey recipientKey = PublicKey.from("RECIPIENTKEY".getBytes()); + final PublicKey anotherRecipient = PublicKey.from("ANOTHERRECIPIENT".getBytes()); + recipients.add(recipientKey); + recipients.add(anotherRecipient); + + final List recipientBoxes = new ArrayList<>(); + final byte[] recipientBox = "box1".getBytes(); + final byte[] anotherRecipientBox = "box2".getBytes(); + recipientBoxes.add(recipientBox); + recipientBoxes.add(anotherRecipientBox); + + when(payload.getRecipientKeys()).thenReturn(recipients); + when(payload.getRecipientBoxes()).thenReturn(recipientBoxes); + + when(encryptedTransactionDAO.retrieveAllTransactions()).thenReturn(singletonList(tx)); + when(payloadEncoder.decode(any(byte[].class))).thenReturn(payload); + + EncodedPayload prunedPayload = mock(EncodedPayload.class); + when(prunedPayload.getRecipientKeys()).thenReturn(singletonList(recipientKey)); + when(prunedPayload.getRecipientBoxes()).thenReturn(singletonList(recipientBox)); + when(payloadEncoder.forRecipient(payload, recipientKey)).thenReturn(prunedPayload); + + final ResendRequest resendRequest = new ResendRequest(); + resendRequest.setPublicKey(recipientKey.encodeToBase64()); + resendRequest.setType(ResendRequestType.ALL); + + ResendResponse result = transactionManager.resend(resendRequest); + + assertThat(result).isNotNull(); + verify(payloadPublisher).publishPayload(eq(prunedPayload), eq(recipientKey)); + + verify(encryptedTransactionDAO).retrieveAllTransactions(); + verify(payloadEncoder).forRecipient(payload, recipientKey); + verify(payloadEncoder).decode(encodedData); + } + + @Test + public void resendAllWhereRequestedIsSenderThenPublishedPayloadIsNotPruned() { + + final byte[] encodedData = "transaction".getBytes(); + final EncryptedTransaction tx = new EncryptedTransaction(mock(MessageHash.class), encodedData); + final EncodedPayload payload = mock(EncodedPayload.class); + + final List recipientBoxes = new ArrayList<>(); + final byte[] recipientBox = "box1".getBytes(); + recipientBoxes.add(recipientBox); + + final PublicKey localKey = PublicKey.from("LOCAL_KEY".getBytes()); + final PublicKey senderKey = PublicKey.from("SENDER".getBytes()); + + final List recipients = new ArrayList<>(); + + when(payload.getSenderKey()).thenReturn(senderKey); + when(payload.getRecipientKeys()).thenReturn(recipients); + when(payload.getRecipientBoxes()).thenReturn(recipientBoxes); + + when(encryptedTransactionDAO.retrieveAllTransactions()).thenReturn(singletonList(tx)); + when(payloadEncoder.decode(any(byte[].class))).thenReturn(payload); + when(enclave.getPublicKeys()).thenReturn(singleton(localKey)); + + final ResendRequest resendRequest = new ResendRequest(); + resendRequest.setPublicKey(senderKey.encodeToBase64()); + resendRequest.setType(ResendRequestType.ALL); + + ResendResponse result = transactionManager.resend(resendRequest); + + assertThat(result).isNotNull(); + verify(payloadPublisher).publishPayload(eq(payload), eq(senderKey)); + verify(payloadEncoder, never()).forRecipient(any(EncodedPayload.class), any(PublicKey.class)); + + verify(encryptedTransactionDAO).retrieveAllTransactions(); + verify(payloadEncoder).decode(encodedData); + verify(enclave).getPublicKeys(); + verify(enclave).unencryptTransaction(payload, localKey); + } + @Test public void resendAllWhereRequestedIsSenderAndRecipientDoesntExist() { @@ -339,6 +426,8 @@ public void resendAllWithOnePayloadAndOneRecipientThenPublishPayloadExceptionIsC when(resendRequest.getPublicKey()).thenReturn(publicKeyEncoded); when(resendRequest.getType()).thenReturn(ResendRequestType.ALL); + when(payloadEncoder.forRecipient(eq(encodedPayload), any(PublicKey.class))).thenReturn(encodedPayload); + doThrow(new PublishPayloadException("msg")) .when(payloadPublisher).publishPayload(encodedPayload, publicKey); @@ -346,6 +435,7 @@ public void resendAllWithOnePayloadAndOneRecipientThenPublishPayloadExceptionIsC verify(payloadPublisher).publishPayload(encodedPayload, publicKey); verify(payloadEncoder).decode(any(byte[].class)); + verify(payloadEncoder).forRecipient(any(EncodedPayload.class), any(PublicKey.class)); verify(encryptedTransactionDAO).retrieveAllTransactions(); } @@ -378,6 +468,9 @@ public void resendAllIfTwoPayloadsAndFirstThrowsExceptionThenSecondIsStillPublis when(resendRequest.getPublicKey()).thenReturn(publicKeyEncoded); when(resendRequest.getType()).thenReturn(ResendRequestType.ALL); + when(payloadEncoder.forRecipient(eq(encodedPayload), any(PublicKey.class))).thenReturn(encodedPayload); + when(payloadEncoder.forRecipient(eq(otherEncodedPayload), any(PublicKey.class))).thenReturn(otherEncodedPayload); + doThrow(new PublishPayloadException("msg")) .when(payloadPublisher).publishPayload(encodedPayload, publicKey); @@ -386,6 +479,7 @@ public void resendAllIfTwoPayloadsAndFirstThrowsExceptionThenSecondIsStillPublis verify(payloadPublisher).publishPayload(encodedPayload, publicKey); verify(payloadPublisher).publishPayload(otherEncodedPayload, publicKey); verify(payloadEncoder, times(2)).decode(any(byte[].class)); + verify(payloadEncoder, times(2)).forRecipient(any(EncodedPayload.class), any(PublicKey.class)); verify(encryptedTransactionDAO).retrieveAllTransactions(); } @@ -413,6 +507,9 @@ public void resendAllTwoPayloadsAndTwoRecipientsAllThrowExceptionButAllStillPubl when(encodedPayload.getRecipientKeys()).thenReturn(Collections.singletonList(publicKey)); when(otherEncodedPayload.getRecipientKeys()).thenReturn(Collections.singletonList(publicKey)); + when(payloadEncoder.forRecipient(encodedPayload, publicKey)).thenReturn(encodedPayload); + when(payloadEncoder.forRecipient(otherEncodedPayload, publicKey)).thenReturn(otherEncodedPayload); + ResendRequest resendRequest = mock(ResendRequest.class); when(resendRequest.getPublicKey()).thenReturn(publicKeyEncoded); when(resendRequest.getType()).thenReturn(ResendRequestType.ALL); @@ -429,6 +526,7 @@ public void resendAllTwoPayloadsAndTwoRecipientsAllThrowExceptionButAllStillPubl verify(payloadPublisher).publishPayload(encodedPayload, publicKey); verify(payloadPublisher).publishPayload(otherEncodedPayload, publicKey); verify(payloadEncoder, times(2)).decode(any(byte[].class)); + verify(payloadEncoder, times(2)).forRecipient(any(EncodedPayload.class), any(PublicKey.class)); verify(payloadPublisher, times(2)).publishPayload(any(EncodedPayload.class), any(PublicKey.class)); } @@ -792,7 +890,7 @@ public void storeRawWithEmptySender() { verify(enclave).encryptRawPayload(eq(payload), eq(PublicKey.from(sender))); verify(enclave).defaultPublicKey(); - + verify(encryptedRawTransactionDAO).save(argThat(et -> { assertThat(et.getEncryptedKey()).containsExactly("SomeKey".getBytes()); assertThat(et.getEncryptedPayload()).containsExactly("CIPHERTEXT".getBytes());