|
2 | 2 |
|
3 | 3 | import co.nstant.in.cbor.CborException; |
4 | 4 | import com.bloxbean.cardano.client.common.model.Network; |
5 | | -import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey; |
6 | 5 | import com.bloxbean.cardano.client.exception.CborDeserializationException; |
7 | 6 | 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; |
16 | 7 | 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; |
22 | 8 | 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; |
26 | 9 |
|
27 | | -import java.util.ArrayList; |
28 | | -import java.util.Collections; |
29 | 10 | 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; |
34 | 11 |
|
35 | 12 | /** |
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> |
37 | 18 | */ |
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); |
186 | 60 |
|
187 | | - private void validateMetadataForStakingCredential(Operation operation) { |
188 | | - if (operation.getMetadata() == null) { |
189 | | - throw ExceptionFactory.missingStakingKeyError(); |
190 | | - } |
191 | | - } |
192 | 61 | } |
0 commit comments