Skip to content

Commit

Permalink
feat: SET-523 support resend v2 of tessera payload
Browse files Browse the repository at this point in the history
  • Loading branch information
rodion-lim-partior committed Jun 14, 2024
1 parent 0cf56da commit a46d7ec
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
/**
* Provides endpoints for dealing with transactions, including:
*
* <p>- creating new transactions and distributing them - deleting transactions - fetching
* <p>
* - creating new transactions and distributing them - deleting transactions -
* fetching
* transactions - resending old transactions
*/
@Tag(name = "peer-to-peer")
Expand All @@ -58,24 +60,8 @@ public TransactionResource(
this.legacyResendManager = Objects.requireNonNull(legacyResendManager);
}

@Operation(
summary = "/resend",
operationId = "requestPayloadResend",
description =
"initiate resend of either an INDIVIDUAL transaction or ALL transactions involving a given public key")
@ApiResponse(
responseCode = "200",
description = "resent payload",
content =
@Content(
array =
@ArraySchema(
schema =
@Schema(
description =
"empty if request was for ALL; else the encoded INDIVIDUAL transaction",
type = "string",
format = "byte"))))
@Operation(summary = "/resend", operationId = "requestPayloadResend", description = "initiate resend of either an INDIVIDUAL transaction or ALL transactions involving a given public key")
@ApiResponse(responseCode = "200", description = "resent payload", content = @Content(array = @ArraySchema(schema = @Schema(description = "empty if request was for ALL; else the encoded INDIVIDUAL transaction", type = "string", format = "byte"))))
@POST
@Path("resend")
@Consumes(APPLICATION_JSON)
Expand All @@ -86,31 +72,28 @@ public Response resend(@Valid @NotNull final ResendRequest resendRequest) {

final PayloadEncoder payloadEncoder = PayloadEncoder.create(EncodedPayloadCodec.LEGACY);

final PublicKey recipient =
Optional.of(resendRequest)
.map(ResendRequest::getPublicKey)
.map(Base64Codec.create()::decode)
.map(PublicKey::from)
.get();

final MessageHash transactionHash =
Optional.of(resendRequest)
.map(ResendRequest::getKey)
.map(Base64.getDecoder()::decode)
.map(MessageHash::new)
.orElse(null);

final com.quorum.tessera.recovery.resend.ResendRequest request =
com.quorum.tessera.recovery.resend.ResendRequest.Builder.create()
.withType(
com.quorum.tessera.recovery.resend.ResendRequest.ResendRequestType.valueOf(
resendRequest.getType()))
.withRecipient(recipient)
.withHash(transactionHash)
.build();

final com.quorum.tessera.recovery.resend.ResendResponse response =
legacyResendManager.resend(request);
final PublicKey recipient = Optional.of(resendRequest)
.map(ResendRequest::getPublicKey)
.map(Base64Codec.create()::decode)
.map(PublicKey::from)
.get();

final MessageHash transactionHash = Optional.of(resendRequest)
.map(ResendRequest::getKey)
.map(Base64.getDecoder()::decode)
.map(MessageHash::new)
.orElse(null);

final com.quorum.tessera.recovery.resend.ResendRequest request = com.quorum.tessera.recovery.resend.ResendRequest.Builder
.create()
.withType(
com.quorum.tessera.recovery.resend.ResendRequest.ResendRequestType.valueOf(
resendRequest.getType()))
.withRecipient(recipient)
.withHash(transactionHash)
.build();

final com.quorum.tessera.recovery.resend.ResendResponse response = legacyResendManager.resend(request);

final Response.ResponseBuilder builder = Response.ok();
Optional.ofNullable(response.getPayload())
Expand All @@ -119,18 +102,50 @@ public Response resend(@Valid @NotNull final ResendRequest resendRequest) {
return builder.build();
}

@Operation(
summary = "/resendBatch",
operationId = "requestPayloadBatchResend",
description = "initiate resend of all transactions for a given public key in batches")
@ApiResponse(
responseCode = "200",
description = "count of total transactions being resent",
content =
@Content(
schema =
@Schema(
implementation = com.quorum.tessera.p2p.recovery.ResendBatchResponse.class)))
@Operation(summary = "/resendV2", operationId = "requestPayloadResend", description = "initiate resend of either an INDIVIDUAL transaction or ALL transactions involving a given public key")
@ApiResponse(responseCode = "200", description = "resent payload", content = @Content(array = @ArraySchema(schema = @Schema(description = "empty if request was for ALL; else the encoded INDIVIDUAL transaction", type = "string", format = "byte"))))
@POST
@Path("resendV2")
@Consumes(APPLICATION_JSON)
@Produces(TEXT_PLAIN)
public Response resendV2(@Valid @NotNull final ResendRequest resendRequest) {

LOGGER.debug("Received resend request");

final PayloadEncoder payloadEncoder = PayloadEncoder.create(EncodedPayloadCodec.LEGACY);

final PublicKey recipient = Optional.of(resendRequest)
.map(ResendRequest::getPublicKey)
.map(Base64Codec.create()::decode)
.map(PublicKey::from)
.get();

final MessageHash transactionHash = Optional.of(resendRequest)
.map(ResendRequest::getKey)
.map(Base64.getDecoder()::decode)
.map(MessageHash::new)
.orElse(null);

final com.quorum.tessera.recovery.resend.ResendRequest request = com.quorum.tessera.recovery.resend.ResendRequest.Builder
.create()
.withType(
com.quorum.tessera.recovery.resend.ResendRequest.ResendRequestType.valueOf(
resendRequest.getType()))
.withRecipient(recipient)
.withHash(transactionHash)
.build();

final com.quorum.tessera.recovery.resend.ResendResponse response = legacyResendManager.resendV2(request);

final Response.ResponseBuilder builder = Response.ok();
Optional.ofNullable(response.getPayload())
.map(payloadEncoder::encode)
.ifPresent(builder::entity);
return builder.build();
}

@Operation(summary = "/resendBatch", operationId = "requestPayloadBatchResend", description = "initiate resend of all transactions for a given public key in batches")
@ApiResponse(responseCode = "200", description = "count of total transactions being resent", content = @Content(schema = @Schema(implementation = com.quorum.tessera.p2p.recovery.ResendBatchResponse.class)))
@POST
@Path("resendBatch")
@Consumes(APPLICATION_JSON)
Expand All @@ -139,62 +154,41 @@ public Response resendBatch(@Valid @NotNull final ResendBatchRequest resendBatch

LOGGER.debug("Received resend request");

final com.quorum.tessera.recovery.resend.ResendBatchRequest request =
com.quorum.tessera.recovery.resend.ResendBatchRequest.Builder.create()
.withPublicKey(resendBatchRequest.getPublicKey())
.withBatchSize(resendBatchRequest.getBatchSize())
.build();
final com.quorum.tessera.recovery.resend.ResendBatchRequest request = com.quorum.tessera.recovery.resend.ResendBatchRequest.Builder
.create()
.withPublicKey(resendBatchRequest.getPublicKey())
.withBatchSize(resendBatchRequest.getBatchSize())
.build();

final ResendBatchResponse response = batchResendManager.resendBatch(request);

final com.quorum.tessera.p2p.recovery.ResendBatchResponse responseEntity =
new com.quorum.tessera.p2p.recovery.ResendBatchResponse();
final com.quorum.tessera.p2p.recovery.ResendBatchResponse responseEntity = new com.quorum.tessera.p2p.recovery.ResendBatchResponse();
responseEntity.setTotal(response.getTotal());

final Response.ResponseBuilder builder = Response.status(Response.Status.OK);
builder.entity(responseEntity);
return builder.build();
}

// path push is overloaded (RecoveryResource & TransactionResource); swagger cannot handle
// path push is overloaded (RecoveryResource & TransactionResource); swagger
// cannot handle
// situations like this so this operation documents both
@Operation(
summary = "/push",
operationId = "pushPayload",
description = "store encoded payload to the server's database")
@ApiResponse(
responseCode = "201",
description = "hash of encoded payload",
content =
@Content(
mediaType = TEXT_PLAIN,
schema =
@Schema(
description = "hash of encrypted payload",
type = "string",
format = "base64")))
@ApiResponse(
responseCode = "403",
description =
"server is in recovery mode and encoded payload is not a Standard Private transaction")
@Operation(summary = "/push", operationId = "pushPayload", description = "store encoded payload to the server's database")
@ApiResponse(responseCode = "201", description = "hash of encoded payload", content = @Content(mediaType = TEXT_PLAIN, schema = @Schema(description = "hash of encrypted payload", type = "string", format = "base64")))
@ApiResponse(responseCode = "403", description = "server is in recovery mode and encoded payload is not a Standard Private transaction")
@POST
@Path("push")
@Consumes(APPLICATION_OCTET_STREAM)
public Response push(
@Schema(description = "encoded payload") final byte[] payload,
@HeaderParam(Constants.API_VERSION_HEADER)
@Parameter(
description = "client's supported API versions",
array = @ArraySchema(schema = @Schema(type = "string")))
final List<String> headers) {
@HeaderParam(Constants.API_VERSION_HEADER) @Parameter(description = "client's supported API versions", array = @ArraySchema(schema = @Schema(type = "string"))) final List<String> headers) {

LOGGER.debug("Received push request");

final Set<String> versions =
Optional.ofNullable(headers).orElse(emptyList()).stream()
.filter(Objects::nonNull)
.flatMap(v -> Arrays.stream(v.split(",")))
.collect(Collectors.toSet());
final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream()
.filter(Objects::nonNull)
.flatMap(v -> Arrays.stream(v.split(",")))
.collect(Collectors.toSet());

final EncodedPayloadCodec codec = EncodedPayloadCodec.getPreferredCodec(versions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface LegacyResendManager {

ResendResponse resend(ResendRequest request);

ResendResponse resendV2(ResendRequest request);

static LegacyResendManager create() {
return ServiceLoaderUtil.loadSingle(ServiceLoader.load(LegacyResendManager.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ public ResendResponse resend(ResendRequest request) {
return resendIndividual(request.getRecipient(), request.getHash());
}

final LegacyWorkflowFactory batchWorkflowFactory =
new LegacyWorkflowFactory(enclave, discovery, payloadPublisher);
final LegacyWorkflowFactory batchWorkflowFactory = new LegacyWorkflowFactory(enclave, discovery, payloadPublisher);

final BatchWorkflow batchWorkflow = batchWorkflowFactory.create();

Expand All @@ -74,50 +73,69 @@ public ResendResponse resend(ResendRequest request) {
return ResendResponse.Builder.create().build();
}

@Override
public ResendResponse resendV2(ResendRequest request) {
if (request.getType() == ResendRequest.ResendRequestType.INDIVIDUAL) {
return resendIndividualV2(request.getRecipient(), request.getHash());
} else {
return ResendResponse.Builder.create().build();
}
}

protected ResendResponse resendIndividualV2(
final PublicKey targetResendKey, final MessageHash messageHash) {
final EncryptedTransaction encryptedTransaction = encryptedTransactionDAO
.retrieveByHash(messageHash)
.orElseThrow(
() -> new TransactionNotFoundException(
"Message with hash " + messageHash + " was not found"));

final EncodedPayload payload = encryptedTransaction.getPayload();
final EncodedPayload formattedPayload = EncodedPayload.Builder.forRecipient(payload, targetResendKey).build();
this.payloadPublisher.publishPayload(formattedPayload, targetResendKey);
return ResendResponse.Builder.create().withPayload(formattedPayload).build();
}

protected ResendResponse resendIndividual(
final PublicKey targetResendKey, final MessageHash messageHash) {
final EncryptedTransaction encryptedTransaction =
encryptedTransactionDAO
.retrieveByHash(messageHash)
.orElseThrow(
() ->
new TransactionNotFoundException(
"Message with hash " + messageHash + " was not found"));
final EncryptedTransaction encryptedTransaction = encryptedTransactionDAO
.retrieveByHash(messageHash)
.orElseThrow(
() -> new TransactionNotFoundException(
"Message with hash " + messageHash + " was not found"));

final EncodedPayload payload = encryptedTransaction.getPayload();

if (payload.getPrivacyMode() != PrivacyMode.STANDARD_PRIVATE) {
throw new EnhancedPrivacyNotSupportedException(
"Cannot resend enhanced privacy transaction in legacy resend");
}
// if (payload.getPrivacyMode() != PrivacyMode.STANDARD_PRIVATE) {
// throw new EnhancedPrivacyNotSupportedException(
// "Cannot resend enhanced privacy transaction in legacy resend");
// }

if (!Objects.equals(payload.getSenderKey(), targetResendKey)) {
final EncodedPayload formattedPayload =
EncodedPayload.Builder.forRecipient(payload, targetResendKey).build();
final EncodedPayload formattedPayload = EncodedPayload.Builder.forRecipient(payload, targetResendKey).build();
System.out.println(formattedPayload);
System.out.println(formattedPayload.getCipherText());
return ResendResponse.Builder.create().withPayload(formattedPayload).build();
}

// split all the boxes out into their own payload
final Set<EncodedPayload> allTxns =
payload.getRecipientBoxes().stream()
.map(
box ->
EncodedPayload.Builder.from(payload)
.withNewRecipientKeys(Collections.emptyList())
.withRecipientBoxes(List.of(box.getData()))
.build())
.collect(Collectors.toSet());
final Set<EncodedPayload> allTxns = payload.getRecipientBoxes().stream()
.map(
box -> EncodedPayload.Builder.from(payload)
.withNewRecipientKeys(Collections.emptyList())
.withRecipientBoxes(List.of(box.getData()))
.build())
.collect(Collectors.toSet());

final BatchWorkflowContext context = new BatchWorkflowContext();
context.setPayloadsToPublish(allTxns);
context.setEncryptedTransaction(encryptedTransaction);

new SearchRecipientKeyForPayload(enclave).execute(context);

final EncodedPayload.Builder builder =
EncodedPayload.Builder.from(payload)
.withNewRecipientKeys(new ArrayList<>())
.withRecipientBoxes(new ArrayList<>());
final EncodedPayload.Builder builder = EncodedPayload.Builder.from(payload)
.withNewRecipientKeys(new ArrayList<>())
.withRecipientBoxes(new ArrayList<>());
context
.getPayloadsToPublish()
.forEach(
Expand Down

0 comments on commit a46d7ec

Please sign in to comment.