Skip to content

Commit

Permalink
RA-139: Optimized construction combine (#211)
Browse files Browse the repository at this point in the history
* RA-139: Optimized construction combine

* RA-139: Fixed test

* RA-139: Increased code coverage
  • Loading branch information
Gennadii-T authored Jun 3, 2024
1 parent b39ed1f commit b74a866
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import jakarta.validation.constraints.NotNull;

import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -84,6 +87,8 @@ public class CardanoConstructionServiceImpl implements CardanoConstructionServic
private final ProtocolParamService protocolParamService;
private final RestTemplate restTemplate;

private ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();

@Value("${cardano.rosetta.NODE_SUBMIT_API_PORT}")
private int nodeSubmitApiPort;
@Value("${cardano.rosetta.CARDANO_NODE_SUBMIT_HOST}")
Expand Down Expand Up @@ -227,31 +232,18 @@ public String buildTransaction(String unsignedTransaction, List<Signatures> sign
String transactionMetadata) {
log.info("[buildTransaction] About to signed a transaction with {} signatures",
signaturesList.size());
TransactionWitnessSet witnesses = getWitnessesForTransaction(signaturesList);

log.info("[buildTransaction] Instantiating transaction body from unsigned transaction bytes");
DataItem[] dataItems;
try {
dataItems = com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.deserialize(
HexUtil.decodeHexString(unsignedTransaction));
} catch (Exception e) {
throw ExceptionFactory.cantCreateSignTransaction();
}
CompletableFuture<TransactionBody> transactionBodyFuture =
CompletableFuture.supplyAsync(() -> deserializeTransactionBody(unsignedTransaction), executorService);
CompletableFuture<TransactionWitnessSet> witnessesFuture =
CompletableFuture.supplyAsync(() -> getWitnessesForTransaction(signaturesList), executorService);
CompletableFuture<AuxiliaryData> auxiliaryDataFuture =
CompletableFuture.supplyAsync(() -> deserializeAuxiliaryData(transactionMetadata), executorService);
try {
TransactionBody transactionBody = TransactionBody.deserialize(
(co.nstant.in.cbor.model.Map) dataItems[0]);
log.info(
"[buildTransaction] Creating transaction using transaction body and extracted witnesses");
AuxiliaryData auxiliaryData = null;
if (!ObjectUtils.isEmpty(transactionMetadata)) {
log.info("[buildTransaction] Adding transaction metadata");
Array array = (Array) CborSerializationUtil.deserialize(
HexUtil.decodeHexString(transactionMetadata));
auxiliaryData = AuxiliaryData.deserialize(
(co.nstant.in.cbor.model.Map) array.getDataItems().getFirst());
}
Transaction transaction = Transaction.builder().auxiliaryData(auxiliaryData)
.witnessSet(witnesses).build();
Transaction transaction = Transaction.builder().auxiliaryData(auxiliaryDataFuture.join())
.witnessSet(witnessesFuture.join()).build();
TransactionBody transactionBody = transactionBodyFuture.join();
transaction.setBody(transactionBody);
Array array = (Array) CborSerializationUtil.deserialize(transaction.serialize());
if (transactionBody.getTtl() == 0) {
Expand All @@ -270,10 +262,6 @@ public String buildTransaction(String unsignedTransaction, List<Signatures> sign
}
return HexUtil.encodeHexString(
com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.serialize(array));
} catch (CborDeserializationException e) {
log.error("{} [buildTransaction] CborDeserializationException while building transaction",
e.getMessage());
throw ExceptionFactory.generalDeserializationError(e.getMessage());
} catch (CborSerializationException e) {
log.error("{} [buildTransaction] CborSerializationException while building transaction",
e.getMessage());
Expand All @@ -288,11 +276,13 @@ public TransactionWitnessSet getWitnessesForTransaction(List<Signatures> signatu
ArrayList<BootstrapWitness> bootstrapWitnesses = new ArrayList<>();
log.info("[getWitnessesForTransaction] Extracting witnesses from signatures");
signaturesList.forEach(signature -> {
VerificationKey vKey = new VerificationKey();
vKey.setCborHex(ObjectUtils.isEmpty(signature) ? null : signature.publicKey());
if (ObjectUtils.isEmpty(signature)) {
return;
}

VerificationKey vKey = new VerificationKey(signature.publicKey());
EraAddressType eraAddressType = CardanoAddressUtils.getEraAddressType(signature.address());
if (!ObjectUtils.isEmpty(signature)) {
if (!ObjectUtils.isEmpty(signature.address()) && eraAddressType == EraAddressType.BYRON) {
if (eraAddressType == EraAddressType.BYRON) {
// byron case
ValidateParseUtil.validateChainCode(signature.chainCode());
ByronAddress byronAddress = new ByronAddress(signature.address());
Expand All @@ -310,7 +300,6 @@ public TransactionWitnessSet getWitnessesForTransaction(List<Signatures> signatu
vKeyWitnesses.add(new VkeyWitness(HexUtil.decodeHexString(vKey.getCborHex()),
HexUtil.decodeHexString(signature.signature())));
}
}
});
log.info("[getWitnessesForTransaction] {} witnesses were extracted to sign transaction",
vKeyWitnesses.size());
Expand Down Expand Up @@ -631,4 +620,30 @@ private Signatures extractSignaturesFromAddress(String address) {
}
throw ExceptionFactory.invalidAddressError(address);
}

private TransactionBody deserializeTransactionBody(String unsignedTransaction) {
log.info("[buildTransaction] Instantiating transaction body from unsigned transaction bytes");
try {
DataItem[] dataItems = com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.deserialize(
HexUtil.decodeHexString(unsignedTransaction));
return TransactionBody.deserialize((co.nstant.in.cbor.model.Map) dataItems[0]);
} catch (Exception e) {
log.error("[buildTransaction] Error deserializing unsigned transaction: {}", e.getMessage());
throw ExceptionFactory.cantCreateSignTransaction();
}
}

private AuxiliaryData deserializeAuxiliaryData(String transactionMetadata) {
if (ObjectUtils.isEmpty(transactionMetadata)) {
return null;
}
try {
log.info("[buildTransaction] Adding transaction metadata");
Array array = (Array) CborSerializationUtil.deserialize(HexUtil.decodeHexString(transactionMetadata));
return AuxiliaryData.deserialize((co.nstant.in.cbor.model.Map) array.getDataItems().getFirst());
} catch (Exception e) {
log.error("[buildTransaction] CborDeserializationException while deserializing transactionMetadata: {}", e.getMessage());
throw ExceptionFactory.generalDeserializationError(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cardanofoundation.rosetta.common.exception;

import java.util.List;
import java.util.concurrent.CompletionException;
import jakarta.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -35,6 +36,16 @@ public ResponseEntity<Error> handleApiException(ApiException apiException) {
return new ResponseEntity<>(apiException.getError(), HttpStatus.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(CompletionException.class)
public ResponseEntity<Error> handleCompletionException(CompletionException exception, HttpServletRequest request) {
Throwable cause = exception.getCause();
if (cause instanceof ApiException apiException) {
log.error("An API exception has raised", apiException);
return new ResponseEntity<>(apiException.getError(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return handleGlobalException(exception, request);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Error> handleMethodArgumentNotValidException(
MethodArgumentNotValidException methodArgumentNotValidException, HttpServletRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
import org.openapitools.client.model.TransactionIdentifierResponse;
import org.openapitools.client.model.Version;

import org.cardanofoundation.rosetta.common.model.cardano.crypto.Signatures;

import static org.cardanofoundation.rosetta.common.util.Constants.*;
import static org.openapitools.client.model.CurveType.EDWARDS25519;

Expand Down Expand Up @@ -343,4 +345,11 @@ public static Operation givenOperation() {
.build())
.build();
}

public static Signatures givenSignatures() {
return new Signatures("dc2a1948bfa9411b37e8d280b04c48a85af5588bcf509c0fca798f7b462ebca92d6733dacc1f1c6c1463623c085401be07ea422ad4f1c543375e7d3d2393aa0b",
"73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d",
"dd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f",
"Ae2tdPwUPEZC6WJfVQxTNN2tWw4skGrN6zRVukvxJmTFy1nYkVGQBuURU3L");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import jakarta.validation.constraints.NotNull;
Expand All @@ -15,6 +17,8 @@
import org.springframework.web.client.RestTemplate;
import co.nstant.in.cbor.CborException;
import com.bloxbean.cardano.client.util.HexUtil;
import com.bloxbean.cardano.yaci.core.exception.CborRuntimeException;
import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
Expand All @@ -23,6 +27,7 @@
import org.openapitools.client.model.Operation;
import org.openapitools.client.model.PublicKey;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -32,13 +37,15 @@
import org.cardanofoundation.rosetta.common.exception.ApiException;
import org.cardanofoundation.rosetta.common.exception.Error;
import org.cardanofoundation.rosetta.common.mapper.CborArrayToTransactionData;
import org.cardanofoundation.rosetta.common.model.cardano.crypto.Signatures;
import org.cardanofoundation.rosetta.common.model.cardano.transaction.TransactionParsed;
import org.cardanofoundation.rosetta.common.util.CardanoAddressUtils;
import org.cardanofoundation.rosetta.common.util.Constants;
import org.cardanofoundation.rosetta.common.util.RosettaConstants.RosettaErrorType;

import static org.cardanofoundation.rosetta.EntityGenerator.givenOperation;
import static org.cardanofoundation.rosetta.EntityGenerator.givenPublicKey;
import static org.cardanofoundation.rosetta.EntityGenerator.givenSignatures;
import static org.cardanofoundation.rosetta.api.construction.enumeration.AddressType.BASE;
import static org.cardanofoundation.rosetta.api.construction.enumeration.AddressType.REWARD;
import static org.cardanofoundation.rosetta.common.enumeration.NetworkEnum.PREPROD;
Expand All @@ -59,17 +66,34 @@
@ExtendWith(MockitoExtension.class)
class CardanoConstructionServiceImplTest {

@Mock
private RestTemplate restTemplate;
private final static String TRANSACTION_SIGNED = "8279025a3833613430303831383235383230326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663031303138323832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666238323139323731306131353831636230643037643435666539353134663830323133663430323065356136313234313435386265363236383431636465373137636233386137613334393437373536393634366634333666363936653139303930363530346137353631366534333732373537613534366636623635366536313761366631393161306134373665373537343633366636393665313932373130383235383164363162623430663161363437626338386331626436623733386462386562363633353764393236343734656135666664366261613736633966623139396334303032313939633430303330306131303038313832353832303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303035383430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303066356636a16a6f7065726174696f6e7381a7746f7065726174696f6e5f6964656e746966696572a265696e646578006d6e6574776f726b5f696e64657800647479706565696e707574667374617475736773756363657373676163636f756e74a16761646472657373783a616464723176786135707564786737376733736461646465636d773874766336686d796e79776e34396c6c747434666d766e3763706e6b63707866616d6f756e74a26576616c7565662d39303030306863757272656e6379a26673796d626f6c6341444168646563696d616c73066b636f696e5f6368616e6765a26f636f696e5f6964656e746966696572a16a6964656e7469666965727842326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663a316b636f696e5f616374696f6e6a636f696e5f7370656e74686d65746164617461a16b746f6b656e42756e646c6581a268706f6c69637949647838623064303764343566653935313466383032313366343032306535613631323431343538626536323638343163646537313763623338613766746f6b656e7383a26576616c756564323331306863757272656e6379a26673796d626f6c7234373735363936343666343336663639366568646563696d616c7300a26576616c756564363636366863757272656e6379a26673796d626f6c7820346137353631366534333732373537613534366636623635366536313761366668646563696d616c7300a26576616c75656531303030306863757272656e6379a26673796d626f6c6e366537353734363336663639366568646563696d616c7300";
private final static String TRANSACTION_NOT_SIGNED = "82790132613530303831383235383230326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663031303138323832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666230313832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666230343032316130353762636566623033313930336538303438313832303138323030353831636262343066316136343762633838633162643662373338646238656236363335376439323634373465613566666436626161373663396662a16a6f7065726174696f6e7382a6746f7065726174696f6e5f6964656e746966696572a265696e646578006d6e6574776f726b5f696e64657800647479706565696e707574667374617475736773756363657373676163636f756e74a16761646472657373783a616464723176786135707564786737376733736461646465636d773874766336686d796e79776e34396c6c747434666d766e3763706e6b63707866616d6f756e74a26576616c7565692d39303030303030306863757272656e6379a26673796d626f6c6341444168646563696d616c73066b636f696e5f6368616e6765a26f636f696e5f6964656e746966696572a16a6964656e7469666965727842326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663a316b636f696e5f616374696f6e6a636f696e5f7370656e74a5746f7065726174696f6e5f6964656e746966696572a165696e646578036474797065767374616b654b65794465726567697374726174696f6e667374617475736773756363657373676163636f756e74a16761646472657373783b7374616b653175387a666e6b687034673676686e6565746d763271656e3766356e64726b6c716a7138653973326e636b3968333063667a36716d70686d65746164617461a2727374616b696e675f63726564656e7469616ca2696865785f62797465737840314234303044363041414633344541463644434241423942424134363030314132333439373838364346313130363646373834363933334433304535414433466a63757276655f747970656c6564776172647332353531396c726566756e64416d6f756e74a26576616c7565682d323030303030306863757272656e6379a26673796d626f6c6341444168646563696d616c7306";
private final static String COMBINE_UNSIGNED_TRANSACTION = "a400818258202f23fd8cca835af21f3ac375bac601f97ead75f2e79143bdf71fe2c4be043e8f01018282581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb19271082581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb199c4002199c40031903e8";
private final static String COMBINE_SIGNED_TRANSACTION = "84a400818258202f23fd8cca835af21f3ac375bac601f97ead75f2e79143bdf71fe2c4be043e8f01018282581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb19271082581d61bb40f1a647bc88c1bd6b738db8eb66357d926474ea5ffd6baa76c9fb199c4002199c40031903e8a1028184582073fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d5840dc2a1948bfa9411b37e8d280b04c48a85af5588bcf509c0fca798f7b462ebca92d6733dacc1f1c6c1463623c085401be07ea422ad4f1c543375e7d3d2393aa0b5820dd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f41a0f5f6";

@Mock
private RestTemplate restTemplate;

@InjectMocks
private CardanoConstructionServiceImpl cardanoService;

private MockedStatic<CompletableFuture> completableFutureMock;

@BeforeEach
void setup() {
cardanoService = new CardanoConstructionServiceImpl(null, null,restTemplate);
completableFutureMock = Mockito.mockStatic(CompletableFuture.class, invocation -> {
if (invocation.getMethod().getName().equals("supplyAsync")) {
Supplier<?> supplier = invocation.getArgument(0);
return CompletableFuture.completedFuture(supplier.get());
}
return invocation.callRealMethod();
});
}

@AfterEach
void tearDown() {
completableFutureMock.close();
}

@Test
Expand Down Expand Up @@ -402,6 +426,53 @@ void getHdPublicKeyFromRosettaKeyTest() {
assertEquals("Invalid public key length", exception.getMessage());
}

@Test
void buildTransaction() {
List<Signatures> signatures = Collections.singletonList(givenSignatures());
String result = cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, "");

assertEquals(COMBINE_SIGNED_TRANSACTION, result);
}

@Test
void buildTransaction_whenCannotDeserializeTransactionBody_thenThrowException() {
List<Signatures> signatures = Collections.singletonList(givenSignatures());

try (MockedStatic<CborSerializationUtil> mocked = Mockito.mockStatic(CborSerializationUtil.class)) {
mocked.when(() -> CborSerializationUtil.deserialize(any(byte[].class)))
.thenThrow(new CborRuntimeException("CborException"));

ApiException result = assertThrows(ApiException.class,
() -> cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, ""));

assertEquals(RosettaErrorType.CANT_CREATE_SIGN_TRANSACTION.getMessage(), result.getError().getMessage());
assertEquals(RosettaErrorType.CANT_CREATE_SIGN_TRANSACTION.getCode(), result.getError().getCode());
assertFalse(result.getError().isRetriable());
mocked.verify(() -> CborSerializationUtil.deserialize(any(byte[].class)), times(1));
}
}

@Test
void buildTransaction_whenCannotDeserializeAuxiliaryData_thenThrowException() {
List<Signatures> signatures = Collections.singletonList(givenSignatures());

try (MockedStatic<com.bloxbean.cardano.client.common.cbor.CborSerializationUtil> mocked
= Mockito.mockStatic(com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.class)) {
mocked.when(() -> com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.deserialize(any()))
.thenThrow(new com.bloxbean.cardano.client.exception.CborRuntimeException("CborException"));

ApiException result = assertThrows(ApiException.class,
() -> cardanoService.buildTransaction(COMBINE_UNSIGNED_TRANSACTION, signatures, COMBINE_UNSIGNED_TRANSACTION));

assertEquals(RosettaErrorType.DESERIALIZATION_ERROR.getMessage(), result.getError().getMessage());
assertEquals(RosettaErrorType.DESERIALIZATION_ERROR.getCode(), result.getError().getCode());
assertEquals("CborException", result.getError().getDetails().getMessage());
assertFalse(result.getError().isRetriable());
mocked.verify(() -> com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.deserialize(any()),
times(1));
}
}

@NotNull
private HttpHeaders given() {
ReflectionTestUtils.setField(cardanoService, "nodeSubmitApiPort", 8080);
Expand Down
Loading

0 comments on commit b74a866

Please sign in to comment.