Skip to content

Commit 6b2f28a

Browse files
author
Mateusz Czeladka
committed
refactor: operations service refactoring plus unit tests.
1 parent da62f24 commit 6b2f28a

File tree

11 files changed

+1704
-658
lines changed

11 files changed

+1704
-658
lines changed

api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/CardanoConstructionServiceImpl.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,27 @@ public class CardanoConstructionServiceImpl implements CardanoConstructionServic
8989
@Override
9090
public TransactionParsed parseTransaction(Network network, String transaction, boolean signed) {
9191
Array decodeTransaction = decodeTransaction(transaction);
92+
9293
try {
9394
TransactionData convertedTr = CborArrayToTransactionData.convert(decodeTransaction, signed);
9495
List<Operation> operations = operationService.getOperationsFromTransactionData(convertedTr, network);
9596
List<AccountIdentifier> accountIdentifierSigners = new ArrayList<>();
97+
9698
if (signed) {
9799
log.info("[parseSignedTransaction] About to get signatures from parsed transaction");
98100
List<String> accumulator = convertedTr.transactionExtraData().operations().stream()
99101
.map(o -> operationService.getSignerFromOperation(network, o))
100102
.flatMap(List::stream)
101103
.toList();
104+
102105
accountIdentifierSigners = getUniqueAccountIdentifiers(accumulator);
103106
}
107+
104108
return new TransactionParsed(operations, accountIdentifierSigners);
105109
} catch (CborException | CborDeserializationException | CborSerializationException error) {
106110
log.error("{} [parseTransaction] Cant instantiate transaction from transaction bytes",
107111
error.getMessage(), error);
112+
108113
throw ExceptionFactory.invalidTransactionError();
109114
}
110115
}
@@ -624,7 +629,7 @@ private TransactionBody deserializeTransactionBody(String unsignedTransaction) {
624629
private List<AccountIdentifier> getUniqueAccountIdentifiers(List<String> addresses) {
625630
return new HashSet<>(addresses)
626631
.stream()
627-
.map(s -> new AccountIdentifier(s, null, null))
632+
.map(address -> new AccountIdentifier(address, null, null))
628633
.toList();
629634
}
630635

api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/OperationService.java

Lines changed: 46 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -2,191 +2,60 @@
22

33
import co.nstant.in.cbor.CborException;
44
import com.bloxbean.cardano.client.common.model.Network;
5-
import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey;
65
import com.bloxbean.cardano.client.exception.CborDeserializationException;
76
import com.bloxbean.cardano.client.exception.CborSerializationException;
8-
import com.bloxbean.cardano.client.transaction.spec.TransactionBody;
9-
import com.bloxbean.cardano.client.transaction.spec.TransactionInput;
10-
import com.bloxbean.cardano.client.transaction.spec.TransactionOutput;
11-
import lombok.extern.slf4j.Slf4j;
12-
import org.apache.commons.lang3.ObjectUtils;
13-
import org.cardanofoundation.rosetta.common.enumeration.OperationType;
14-
import org.cardanofoundation.rosetta.common.exception.ExceptionFactory;
15-
import org.cardanofoundation.rosetta.common.model.cardano.pool.PoolRegistrationCertReturn;
167
import org.cardanofoundation.rosetta.common.model.cardano.transaction.TransactionData;
17-
import org.cardanofoundation.rosetta.common.model.cardano.transaction.TransactionExtraData;
18-
import org.cardanofoundation.rosetta.common.util.CardanoAddressUtils;
19-
import org.cardanofoundation.rosetta.common.util.Constants;
20-
import org.cardanofoundation.rosetta.common.util.ParseConstructionUtil;
21-
import org.cardanofoundation.rosetta.common.util.ValidateParseUtil;
228
import org.openapitools.client.model.Operation;
23-
import org.openapitools.client.model.OperationIdentifier;
24-
import org.openapitools.client.model.OperationMetadata;
25-
import org.springframework.stereotype.Service;
269

27-
import java.util.ArrayList;
28-
import java.util.Collections;
2910
import java.util.List;
30-
import java.util.Optional;
31-
32-
import static org.cardanofoundation.rosetta.common.util.Constants.OPERATION_TYPE_POOL_REGISTRATION;
33-
import static org.cardanofoundation.rosetta.common.util.Constants.OPERATION_TYPE_POOL_REGISTRATION_WITH_CERT;
3411

3512
/**
36-
* Service class to operate on Operations
13+
* Service interface for processing and extracting Rosetta operations from Cardano transactions.
14+
* <p>
15+
* This service handles the conversion between Cardano transaction structures and Rosetta API
16+
* operations, including inputs, outputs, certificates (staking, pool, governance), and withdrawals.
17+
* </p>
3718
*/
38-
@Slf4j
39-
@Service
40-
public class OperationService {
41-
42-
public List<Operation> getOperationsFromTransactionData(TransactionData data, Network network)
43-
throws CborDeserializationException, CborException, CborSerializationException {
44-
TransactionBody transactionBody = data.transactionBody();
45-
TransactionExtraData extraData = data.transactionExtraData();
46-
47-
List<Operation> operations = new ArrayList<>();
48-
fillInputOperations(transactionBody, extraData, operations);
49-
fillOutputOperations(transactionBody, operations);
50-
fillCertOperations(transactionBody, extraData, network, operations);
51-
fillWithdrawalOperations(transactionBody, extraData, network, operations);
52-
fillGovOperations(extraData, operations, network);
53-
54-
return operations;
55-
}
56-
57-
private void fillGovOperations(TransactionExtraData transactionBody, List<Operation> operations, Network network) {
58-
List<Operation> governanceOperations = transactionBody.operations().stream()
59-
.filter(o -> Constants.GOVERNANCE_OPERATIONS.contains(o.getType())
60-
).toList();
61-
62-
operations.addAll(governanceOperations);
63-
}
64-
65-
public List<String> getSignerFromOperation(Network network, Operation operation) {
66-
if (Constants.POOL_OPERATIONS.contains(operation.getType())) {
67-
return getPoolSigners(network, operation);
68-
}
69-
if (operation.getAccount() != null) {
70-
// org.openapitools.client.model.AccountIdentifier.getAddress() is always not null
71-
return Collections.singletonList(operation.getAccount().getAddress());
72-
}
73-
validateMetadataForStakingCredential(operation);
74-
HdPublicKey hdPublicKey =
75-
CardanoAddressUtils.publicKeyToHdPublicKey(operation.getMetadata().getStakingCredential());
76-
77-
return Collections.singletonList(
78-
CardanoAddressUtils.generateRewardAddress(network, hdPublicKey));
79-
}
80-
81-
private void fillInputOperations(TransactionBody transactionBody,
82-
TransactionExtraData extraData,
83-
List<Operation> operations) {
84-
List<TransactionInput> inputs = transactionBody.getInputs();
85-
log.info("[fillInputOperations] About to parse {} inputs", inputs.size());
86-
List<Operation> extraDataInputOperations = extraData.operations().stream()
87-
.filter(o -> o.getType().equals(OperationType.INPUT.getValue()))
88-
.toList();
89-
90-
for (int i = 0; i < inputs.size(); i++) {
91-
if (!extraDataInputOperations.isEmpty() && extraDataInputOperations.size() <= inputs.size()) {
92-
operations.add(extraDataInputOperations.get(i));
93-
} else { // fallback in case of no extra data input operations
94-
TransactionInput input = inputs.get(i);
95-
96-
Operation inputParsed = ParseConstructionUtil.transactionInputToOperation(input,
97-
(long) operations.size());
98-
99-
operations.add(inputParsed);
100-
}
101-
}
102-
}
103-
104-
private void fillOutputOperations(TransactionBody transactionBody, List<Operation> operations) {
105-
List<TransactionOutput> outputs = transactionBody.getOutputs();
106-
List<OperationIdentifier> relatedOperations = ParseConstructionUtil.getRelatedOperationsFromInputs(
107-
operations);
108-
109-
log.info("[parseOperationsFromTransactionBody] About to parse {} outputs", outputs.size());
110-
111-
for (TransactionOutput output : outputs) {
112-
Operation outputParsed = ParseConstructionUtil.transActionOutputToOperation(output,
113-
(long) operations.size(),
114-
relatedOperations);
115-
operations.add(outputParsed);
116-
}
117-
}
118-
119-
private void fillCertOperations(TransactionBody transactionBody, TransactionExtraData extraData,
120-
Network network, List<Operation> operations)
121-
throws CborException, CborSerializationException {
122-
List<Operation> certOps = extraData.operations().stream()
123-
.filter(o -> {
124-
return Constants.STAKE_POOL_OPERATIONS.contains(o.getType())
125-
|| OperationType.VOTE_DREP_DELEGATION.getValue().equals(o.getType());
126-
}
127-
).toList();
128-
129-
List<Operation> parsedCertOperations = ParseConstructionUtil.parseCertsToOperations(
130-
transactionBody, certOps, network);
131-
132-
operations.addAll(parsedCertOperations);
133-
}
134-
135-
private void fillWithdrawalOperations(TransactionBody transactionBody,
136-
TransactionExtraData extraData,
137-
Network network, List<Operation> operations) {
138-
List<Operation> withdrawalOps = extraData.operations().stream()
139-
.filter(o -> o.getType().equals(OperationType.WITHDRAWAL.getValue()))
140-
.toList();
141-
int withdrawalsCount = ObjectUtils.isEmpty(transactionBody.getWithdrawals()) ? 0
142-
: transactionBody.getWithdrawals().size();
143-
List<Operation> withdrawalsOperations = ParseConstructionUtil.parseWithdrawalsToOperations(
144-
withdrawalOps, withdrawalsCount, network);
145-
operations.addAll(withdrawalsOperations);
146-
}
147-
148-
private List<String> getPoolSigners(Network network, Operation operation) {
149-
List<String> signers = new ArrayList<>();
150-
switch (operation.getType()) {
151-
case OPERATION_TYPE_POOL_REGISTRATION -> {
152-
if (ValidateParseUtil.validateAddressPresence(operation)) {
153-
signers.add(operation.getAccount().getAddress());
154-
}
155-
Optional.ofNullable(operation.getMetadata())
156-
.map(OperationMetadata::getPoolRegistrationParams)
157-
.ifPresent(poolRegistrationParameters -> {
158-
signers.add(poolRegistrationParameters.getRewardAddress());
159-
signers.addAll(poolRegistrationParameters.getPoolOwners());
160-
});
161-
}
162-
case OPERATION_TYPE_POOL_REGISTRATION_WITH_CERT -> {
163-
String poolCertAsHex = Optional.ofNullable(operation.getMetadata())
164-
.map(OperationMetadata::getPoolRegistrationCert)
165-
.orElse(null);
166-
PoolRegistrationCertReturn dto = ValidateParseUtil.validateAndParsePoolRegistrationCert(
167-
network,
168-
poolCertAsHex,
169-
operation.getAccount() == null ? null : operation.getAccount().getAddress()
170-
);
171-
signers.addAll(dto.getAddress());
172-
}
173-
174-
// pool retirement case
175-
default -> {
176-
if (ValidateParseUtil.validateAddressPresence(operation)) {
177-
signers.add(operation.getAccount().getAddress());
178-
}
179-
}
180-
}
181-
log.info("[getPoolSigners] About to return {} signers for {} operation", signers.size(),
182-
operation.getType());
183-
184-
return signers;
185-
}
19+
public interface OperationService {
20+
21+
/**
22+
* Extracts and constructs a complete list of Rosetta operations from transaction data.
23+
* <p>
24+
* This method processes all operation types from a Cardano transaction, including:
25+
* <ul>
26+
* <li>Input operations - UTxO consumption</li>
27+
* <li>Output operations - UTxO creation</li>
28+
* <li>Certificate operations - Staking registrations, delegations, pool operations</li>
29+
* <li>Governance operations - DRep vote delegations, pool governance votes</li>
30+
* <li>Withdrawal operations - Staking reward withdrawals</li>
31+
* </ul>
32+
* </p>
33+
*
34+
* @param data the transaction data containing the transaction body and extra metadata
35+
* @param network the Cardano network (mainnet/testnet) for address generation
36+
* @return a list of Rosetta operations representing all transaction activities
37+
* @throws CborDeserializationException if CBOR deserialization fails
38+
* @throws CborException if CBOR processing encounters an error
39+
* @throws CborSerializationException if CBOR serialization fails
40+
*/
41+
List<Operation> getOperationsFromTransactionData(TransactionData data, Network network)
42+
throws CborDeserializationException, CborException, CborSerializationException;
43+
44+
/**
45+
* Determines the required signers for a given operation.
46+
* <p>
47+
* Different operation types require different signers:
48+
* <ul>
49+
* <li>Pool operations - Pool owners, reward address, and optionally payment address</li>
50+
* <li>Staking operations - Stake address derived from staking credential</li>
51+
* <li>Regular operations - Account address from the operation</li>
52+
* </ul>
53+
* </p>
54+
*
55+
* @param network the Cardano network for address generation
56+
* @param operation the operation to extract signers from
57+
* @return list of addresses that must sign this operation
58+
*/
59+
List<String> getSignerFromOperation(Network network, Operation operation);
18660

187-
private void validateMetadataForStakingCredential(Operation operation) {
188-
if (operation.getMetadata() == null) {
189-
throw ExceptionFactory.missingStakingKeyError();
190-
}
191-
}
19261
}

0 commit comments

Comments
 (0)