Skip to content

Commit 5db564e

Browse files
BerezinDKammerlo
andauthored
refactor: RA-52: Change mapper to work with an assets metadata.
* refactor: transaction names to enum * fix: changed how to get the symbol name to match TS variant. * feat: RA-52: Added tests for the account API --------- Co-authored-by: Kammerlo <[email protected]>
1 parent fd7df3c commit 5db564e

File tree

12 files changed

+258
-113
lines changed

12 files changed

+258
-113
lines changed
Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package org.cardanofoundation.rosetta.api.account.model.domain;
22

3-
import java.math.BigInteger;
43
import java.util.List;
54

65
import lombok.AllArgsConstructor;
76
import lombok.Builder;
87
import lombok.Data;
98
import lombok.NoArgsConstructor;
109

11-
import org.cardanofoundation.rosetta.api.account.model.entity.AddressUtxoEntity;
12-
import org.cardanofoundation.rosetta.api.block.model.entity.UtxoKey;
13-
1410
@Data
1511
@AllArgsConstructor
1612
@NoArgsConstructor
@@ -26,15 +22,4 @@ public Utxo(String txHash, Integer outputIndex) {
2622
this.txHash = txHash;
2723
this.outputIndex = outputIndex;
2824
}
29-
30-
public static Utxo fromUtxoKey(UtxoKey utxoKey) {
31-
return new Utxo(utxoKey.getTxHash(), utxoKey.getOutputIndex());
32-
}
33-
34-
public void populateFromUtxoEntity(AddressUtxoEntity entity) {
35-
this.txHash = entity.getTxHash();
36-
this.outputIndex = entity.getOutputIndex();
37-
this.ownerAddr = entity.getOwnerAddr();
38-
this.amounts = entity.getAmounts();
39-
}
4025
}

api/src/main/java/org/cardanofoundation/rosetta/api/block/mapper/BlockTxToEntity.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ public BlockTx fromEntity(TxnEntity model) {
3535
.setPostConverter(ctx -> {
3636
TxnEntity source = ctx.getSource();
3737
BlockTx dest = ctx.getDestination();
38-
dest.setInputs(source.getInputKeys().stream().map(Utxo::fromUtxoKey).toList());
39-
dest.setOutputs(source.getOutputKeys().stream().map(Utxo::fromUtxoKey).toList());
38+
dest.setInputs(source.getInputKeys().stream()
39+
.map(utxoKey -> modelMapper.map(utxoKey, Utxo.class)).toList());
40+
dest.setOutputs(source.getOutputKeys().stream()
41+
.map(utxoKey -> modelMapper.map(utxoKey, Utxo.class)).toList());
4042
dest.setFee(Optional
4143
.ofNullable(source.getFee())
4244
.map(BigInteger::toString)

api/src/main/java/org/cardanofoundation/rosetta/common/mapper/DataMapper.java

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,50 @@
11
package org.cardanofoundation.rosetta.common.mapper;
22

3-
import com.bloxbean.cardano.client.common.model.Network;
43
import java.math.BigDecimal;
4+
import java.math.BigInteger;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import javax.annotation.Nullable;
10+
511
import lombok.RequiredArgsConstructor;
612
import lombok.extern.slf4j.Slf4j;
7-
import org.apache.commons.codec.binary.Hex;
13+
14+
import com.bloxbean.cardano.client.common.model.Network;
815
import org.apache.commons.lang3.ObjectUtils;
16+
import org.openapitools.client.model.AccountBalanceResponse;
17+
import org.openapitools.client.model.AccountCoinsResponse;
18+
import org.openapitools.client.model.AccountIdentifier;
19+
import org.openapitools.client.model.Amount;
20+
import org.openapitools.client.model.BlockIdentifier;
21+
import org.openapitools.client.model.Coin;
22+
import org.openapitools.client.model.CoinAction;
23+
import org.openapitools.client.model.CoinChange;
24+
import org.openapitools.client.model.CoinIdentifier;
25+
import org.openapitools.client.model.CoinTokens;
26+
import org.openapitools.client.model.ConstructionMetadataResponse;
27+
import org.openapitools.client.model.ConstructionMetadataResponseMetadata;
28+
import org.openapitools.client.model.Currency;
29+
import org.openapitools.client.model.CurrencyMetadata;
30+
import org.openapitools.client.model.NetworkIdentifier;
31+
import org.openapitools.client.model.NetworkListResponse;
32+
import org.openapitools.client.model.NetworkStatusResponse;
33+
import org.openapitools.client.model.Peer;
34+
import org.openapitools.client.model.Signature;
35+
936
import org.cardanofoundation.rosetta.api.account.model.domain.AddressBalance;
37+
import org.cardanofoundation.rosetta.api.account.model.domain.Amt;
1038
import org.cardanofoundation.rosetta.api.account.model.domain.Utxo;
11-
import org.cardanofoundation.rosetta.api.block.model.domain.*;
1239
import org.cardanofoundation.rosetta.api.block.model.domain.Block;
40+
import org.cardanofoundation.rosetta.api.block.model.domain.GenesisBlock;
41+
import org.cardanofoundation.rosetta.api.block.model.domain.NetworkStatus;
42+
import org.cardanofoundation.rosetta.api.block.model.domain.ProtocolParams;
43+
import org.cardanofoundation.rosetta.api.block.model.domain.StakeAddressBalance;
1344
import org.cardanofoundation.rosetta.common.annotation.PersistenceMapper;
45+
import org.cardanofoundation.rosetta.common.enumeration.NetworkEnum;
1446
import org.cardanofoundation.rosetta.common.model.cardano.crypto.Signatures;
1547
import org.cardanofoundation.rosetta.common.util.Constants;
16-
import org.cardanofoundation.rosetta.api.block.model.domain.ProtocolParams;
17-
import org.cardanofoundation.rosetta.common.enumeration.NetworkEnum;
18-
import org.openapitools.client.model.*;
19-
import org.openapitools.client.model.Currency;
20-
import org.springframework.stereotype.Component;
21-
import java.util.*;
2248

2349

2450
@Slf4j
@@ -156,29 +182,57 @@ public static AccountBalanceResponse mapToStakeAddressBalanceResponse(Block bloc
156182
.build();
157183
}
158184

159-
public static AccountCoinsResponse mapToAccountCoinsResponse(Block block,
160-
List<Utxo> utxos) {
185+
public static AccountCoinsResponse mapToAccountCoinsResponse(Block block, List<Utxo> utxos) {
161186
return AccountCoinsResponse.builder()
162187
.blockIdentifier(BlockIdentifier.builder()
163188
.hash(block.getHash())
164189
.index(block.getNumber())
165190
.build())
166-
.coins(utxos.stream().map(utxo -> Coin.builder()
167-
.coinIdentifier(CoinIdentifier.builder()
168-
.identifier(utxo.getTxHash() + ":" + utxo.getOutputIndex())
169-
.build())
170-
.amount(Amount.builder()
171-
.value(utxo.getAmounts().getFirst().getQuantity().toString()) // TODO stream through amount list
172-
.currency(Currency.builder()
173-
.symbol(utxo.getAmounts().getFirst().getUnit()) // TODO stream through amount list
174-
.decimals(Constants.MULTI_ASSET_DECIMALS)
175-
.build())
176-
.build())
177-
.build())
191+
.coins(utxos.stream().map(utxo -> {
192+
Amt adaAsset = utxo.getAmounts().stream()
193+
.filter(amt -> Constants.LOVELACE.equals(amt.getAssetName()))
194+
.findFirst()
195+
.orElseGet(() -> new Amt(null, null, Constants.ADA, BigInteger.ZERO));
196+
return Coin.builder()
197+
.coinIdentifier(CoinIdentifier.builder()
198+
.identifier(utxo.getTxHash() + ":" + utxo.getOutputIndex())
199+
.build())
200+
.amount(Amount.builder()
201+
.value(adaAsset.getQuantity().toString()) // In the DB only Lovelace are persisted.
202+
.currency(Currency.builder()
203+
.symbol(Constants.ADA)
204+
.decimals(Constants.ADA_DECIMALS)
205+
.build())
206+
.build())
207+
.metadata(mapCoinMetadata(utxo))
208+
.build();
209+
})
178210
.toList())
179211
.build();
180212
}
181213

214+
@Nullable
215+
private static Map<String, List<CoinTokens>> mapCoinMetadata(Utxo utxo) {
216+
String key = utxo.getTxHash() + ":" + utxo.getOutputIndex();
217+
List<CoinTokens> coinTokens =
218+
utxo.getAmounts().stream()
219+
.filter(Objects::nonNull)
220+
.filter(amount -> amount.getPolicyId() != null
221+
&& amount.getAssetName() != null
222+
&& amount.getQuantity() != null)
223+
.map(amount -> {
224+
CoinTokens tokens = new CoinTokens();
225+
tokens.setPolicyId(amount.getPolicyId());
226+
tokens.setTokens(List.of(mapAmount(amount.getQuantity().toString(),
227+
// unit = assetName + policyId. To get the symbol policy ID must be removed from Unit. According to CIP67
228+
amount.getUnit().replace(amount.getPolicyId(), ""),
229+
Constants.MULTI_ASSET_DECIMALS, new CurrencyMetadata(amount.getPolicyId()))));
230+
return tokens;
231+
})
232+
.toList();
233+
return coinTokens.isEmpty() ? null : Map.of(key, coinTokens);
234+
}
235+
182236
public ConstructionMetadataResponse mapToMetadataResponse(ProtocolParams protocolParams, Long ttl, Long suggestedFee) {
183237
return ConstructionMetadataResponse.builder()
184238
.metadata(ConstructionMetadataResponseMetadata.builder()

api/src/main/java/org/cardanofoundation/rosetta/common/services/impl/PostgresLedgerDataProviderService.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
import lombok.RequiredArgsConstructor;
1010
import lombok.extern.slf4j.Slf4j;
1111

12-
import org.cardanofoundation.rosetta.api.block.mapper.WithdrawalEntityToWithdrawal;
13-
import org.cardanofoundation.rosetta.api.block.model.repository.WithdrawalRepository;
1412
import org.springframework.stereotype.Component;
1513
import org.apache.commons.lang3.ObjectUtils;
14+
import org.modelmapper.ModelMapper;
1615
import org.openapitools.client.model.Currency;
1716

1817
import org.cardanofoundation.rosetta.api.account.model.domain.AddressBalance;
@@ -24,6 +23,7 @@
2423
import org.cardanofoundation.rosetta.api.account.model.repository.AddressUtxoRepository;
2524
import org.cardanofoundation.rosetta.api.block.mapper.BlockToEntity;
2625
import org.cardanofoundation.rosetta.api.block.mapper.BlockTxToEntity;
26+
import org.cardanofoundation.rosetta.api.block.mapper.WithdrawalEntityToWithdrawal;
2727
import org.cardanofoundation.rosetta.api.block.model.domain.Block;
2828
import org.cardanofoundation.rosetta.api.block.model.domain.BlockTx;
2929
import org.cardanofoundation.rosetta.api.block.model.domain.Delegation;
@@ -46,10 +46,11 @@
4646
import org.cardanofoundation.rosetta.api.block.model.repository.StakeAddressRepository;
4747
import org.cardanofoundation.rosetta.api.block.model.repository.StakeRegistrationRepository;
4848
import org.cardanofoundation.rosetta.api.block.model.repository.TxRepository;
49+
import org.cardanofoundation.rosetta.api.block.model.repository.WithdrawalRepository;
4950
import org.cardanofoundation.rosetta.common.exception.ExceptionFactory;
5051
import org.cardanofoundation.rosetta.common.mapper.ProtocolParamsToEntity;
51-
import org.cardanofoundation.rosetta.common.services.ProtocolParamService;
5252
import org.cardanofoundation.rosetta.common.services.LedgerDataProviderService;
53+
import org.cardanofoundation.rosetta.common.services.ProtocolParamService;
5354
import org.cardanofoundation.rosetta.common.util.Formatters;
5455

5556
@Slf4j
@@ -71,6 +72,7 @@ public class PostgresLedgerDataProviderService implements LedgerDataProviderServ
7172

7273
private final ProtocolParamService protocolParamService;
7374

75+
private final ModelMapper mapper;
7476
private final BlockToEntity mapperBlock;
7577
private final BlockTxToEntity mapperTran;
7678
private final ProtocolParamsToEntity mapperProtocolParams;
@@ -146,7 +148,8 @@ private void populateUtxos(List<Utxo> inputs) {
146148
AddressUtxoEntity first = addressUtxoRepository.findAddressUtxoEntitiesByOutputIndexAndTxHash(
147149
utxo.getOutputIndex(), utxo.getTxHash()).getFirst();
148150
if (first != null) {
149-
utxo.populateFromUtxoEntity(first);
151+
// Populating the values from entity to model
152+
mapper.map(first, utxo);
150153
}
151154
}
152155
}
@@ -174,10 +177,9 @@ public Long findLatestBlockNumber() {
174177
@Override
175178
public List<Utxo> findUtxoByAddressAndCurrency(String address, List<Currency> currencies) {
176179
List<AddressUtxoEntity> addressUtxoEntities = addressUtxoRepository.findUtxosByAddress(address);
177-
List<Utxo> list = addressUtxoEntities.stream()
180+
return addressUtxoEntities.stream()
178181
.map(entity -> createUtxoModel(currencies, entity))
179182
.toList();
180-
return list;
181183
}
182184

183185
@Override
@@ -227,10 +229,10 @@ public ProtocolParams findProtocolParametersFromIndexerAndConfig() {
227229
return mapperProtocolParams.merge(protocolParamService.getProtocolParameters(), protocolParams);
228230
}
229231

230-
private static Utxo createUtxoModel(List<Currency> currencies, AddressUtxoEntity entity) {
231-
Utxo utxoModel = Utxo.fromUtxoKey(
232-
UtxoKey.builder().outputIndex(entity.getOutputIndex()).txHash(entity.getTxHash())
233-
.build());
232+
private Utxo createUtxoModel(List<Currency> currencies, AddressUtxoEntity entity) {
233+
Utxo utxoModel = mapper.map(
234+
UtxoKey.builder().outputIndex(entity.getOutputIndex()).txHash(entity.getTxHash()).build(),
235+
Utxo.class);
234236
utxoModel.setAmounts(getAmts(currencies, entity));
235237
return utxoModel;
236238
}

api/src/main/resources/rosetta-specifications-1.4.15/api.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,8 @@ components:
981981
$ref: '#/components/schemas/CoinIdentifier'
982982
amount:
983983
$ref: '#/components/schemas/Amount'
984+
metadata:
985+
$ref: '#/components/schemas/CoinMetadata'
984986
BalanceExemption:
985987
description: BalanceExemption indicates that the balance for an exempt account could change without a corresponding Operation. This typically occurs with staking rewards, vesting balances, and Currencies with a dynamic supply. Currently, it is possible to exempt an account from strict reconciliation by SubAccountIdentifier.Address or by Currency. This means that any account with SubAccountIdentifier.Address would be exempt or any balance of a particular Currency would be exempt, respectively. BalanceExemptions should be used sparingly as they may introduce significant complexity for integrators that attempt to reconcile all account balance changes. If your implementation relies on any BalanceExemptions, you MUST implement historical balance lookup (the ability to query an account balance at any BlockIdentifier).
986988
type: object
@@ -1951,3 +1953,25 @@ components:
19511953
type: number
19521954
protocol_parameters:
19531955
$ref: '#/components/schemas/ProtocolParameters'
1956+
CoinMetadata:
1957+
description: CoinMetadata is used to provide additional data for a Coin object. It is often used to store human-readable information that a client can use to display information to a user.
1958+
type: object
1959+
additionalProperties:
1960+
type: array
1961+
items:
1962+
$ref: '#/components/schemas/CoinTokens'
1963+
CoinTokens:
1964+
description: |
1965+
CoinTokens contains the policyId and the tokens
1966+
it represents.
1967+
type: object
1968+
required:
1969+
- policyId
1970+
- tokens
1971+
properties:
1972+
policyId:
1973+
type: string
1974+
tokens:
1975+
type: array
1976+
items:
1977+
$ref: '#/components/schemas/Amount'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
description: |
2+
CoinMetadata is used to provide additional data for a Coin object. It is often used to store
3+
human-readable information that a client can use to display information to a user.
4+
type: object
5+
properties:
6+
type: object
7+
additionalProperties:
8+
type: array
9+
items:
10+
$ref: 'CoinTokens.yaml'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
description: |
2+
CoinTokens contains the policyId and the tokens
3+
it represents.
4+
type: object
5+
required:
6+
- policyId
7+
- tokens
8+
properties:
9+
policyId:
10+
type: string
11+
tokens:
12+
type: array
13+
items:
14+
$ref: 'Amount.yaml'

api/src/test/java/org/cardanofoundation/rosetta/api/SpringMappersTestConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
@TestConfiguration
99
@ComponentScan(basePackages = {
10+
"org.cardanofoundation.rosetta.api.account.mapper",
1011
"org.cardanofoundation.rosetta.api.block.mapper",
1112
"org.cardanofoundation.rosetta.common.mapper"})
1213
public class SpringMappersTestConfig {

api/src/test/java/org/cardanofoundation/rosetta/api/account/model/domain/UtxoTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,35 @@
22

33
import java.math.BigInteger;
44
import java.util.Collections;
5+
import jakarta.inject.Inject;
6+
7+
import org.modelmapper.ModelMapper;
58

69
import org.junit.jupiter.api.Test;
710

11+
import org.cardanofoundation.rosetta.api.BaseMapperTest;
812
import org.cardanofoundation.rosetta.api.account.model.entity.AddressUtxoEntity;
913
import org.cardanofoundation.rosetta.api.block.model.entity.UtxoKey;
1014

1115
import static org.junit.jupiter.api.Assertions.assertEquals;
1216
import static org.junit.jupiter.api.Assertions.assertThrows;
1317
import static org.junit.jupiter.api.Assertions.assertTrue;
1418

15-
class UtxoTest {
19+
class UtxoTest extends BaseMapperTest {
20+
21+
@Inject
22+
ModelMapper mapper;
1623

1724
@Test
1825
void fromUtxoKeyPositiveTest() {
19-
Utxo utxo = Utxo.fromUtxoKey(new UtxoKey("txHash", 1));
26+
Utxo utxo = mapper.map(new UtxoKey("txHash", 1), Utxo.class);
2027
assertEquals("txHash", utxo.getTxHash());
2128
assertEquals(1, utxo.getOutputIndex());
2229
}
2330

2431
@Test
2532
void fromUtxoKeyNullTest() {
26-
assertThrows(NullPointerException.class, () -> Utxo.fromUtxoKey(null));
33+
assertThrows(IllegalArgumentException.class, () -> mapper.map(null, Utxo.class));
2734
}
2835

2936
@Test
@@ -33,7 +40,7 @@ void populateFromUtxoEntityPositiveTest() {
3340
entity.setAmounts(Collections.emptyList());
3441
Utxo utxo = new Utxo("txHash", 1);
3542

36-
utxo.populateFromUtxoEntity(entity);
43+
mapper.map(entity, utxo);
3744

3845
assertEquals("ownerAddr", utxo.getOwnerAddr());
3946
assertEquals(Collections.emptyList(), utxo.getAmounts());
@@ -42,6 +49,6 @@ void populateFromUtxoEntityPositiveTest() {
4249
@Test
4350
void populateFromUtxoEntityNullTest() {
4451
Utxo utxo = new Utxo("txHash", 1);
45-
assertThrows(NullPointerException.class, () -> utxo.populateFromUtxoEntity(null));
52+
assertThrows(IllegalArgumentException.class, () -> mapper.map(null, utxo));
4653
}
4754
}

0 commit comments

Comments
 (0)