diff --git a/build.gradle b/build.gradle index f818cb6a..f50716e8 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.mockito:mockito-inline:3.7.7' testImplementation 'org.mockito:mockito-junit-jupiter:3.7.7' + testImplementation('org.assertj:assertj-core:3.21.0') testRuntimeOnly 'org.slf4j:slf4j-log4j12:1.7.30' } diff --git a/src/main/java/com/bloxbean/cardano/client/backend/api/helper/UtxoSelectionStrategy.java b/src/main/java/com/bloxbean/cardano/client/backend/api/helper/UtxoSelectionStrategy.java index c3a4fd04..a080271f 100644 --- a/src/main/java/com/bloxbean/cardano/client/backend/api/helper/UtxoSelectionStrategy.java +++ b/src/main/java/com/bloxbean/cardano/client/backend/api/helper/UtxoSelectionStrategy.java @@ -1,6 +1,5 @@ package com.bloxbean.cardano.client.backend.api.helper; -import com.bloxbean.cardano.client.backend.common.OrderEnum; import com.bloxbean.cardano.client.backend.exception.ApiException; import com.bloxbean.cardano.client.backend.model.Utxo; @@ -19,8 +18,24 @@ public interface UtxoSelectionStrategy { * @param unit Unit * @param amount Amount * @param excludeUtxos Utxos to ignore - * @return + * @return List of Utxos */ - public List selectUtxos(String address, String unit, BigInteger amount, Set excludeUtxos) throws ApiException; + List selectUtxos(String address, String unit, BigInteger amount, Set excludeUtxos) throws ApiException; + + /** + * + * @return True if utxos with datum hash need to ignored, otherwise false + */ + default boolean ignoreUtxosWithDatumHash() { + return true; + } + + /** + * Set if utxos with datum hash need to be ignored or not + * @param ignoreUtxosWithDatumHash + */ + default void setIgnoreUtxosWithDatumHash(boolean ignoreUtxosWithDatumHash) { + + } } diff --git a/src/main/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImpl.java b/src/main/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImpl.java index 127bef9e..65be93cb 100644 --- a/src/main/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImpl.java +++ b/src/main/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImpl.java @@ -21,8 +21,11 @@ public class DefaultUtxoSelectionStrategyImpl implements UtxoSelectionStrategy { private UtxoService utxoService; + private boolean ignoreUtxosWithDatumHash; + public DefaultUtxoSelectionStrategyImpl(UtxoService utxoService) { this.utxoService = utxoService; + this.ignoreUtxosWithDatumHash = true; } @Override @@ -33,7 +36,7 @@ public List selectUtxos(String address, String unit, BigInteger amount, Se BigInteger totalUtxoAmount = BigInteger.valueOf(0); List selectedUtxos = new ArrayList<>(); boolean canContinue = true; - int i = 0; + int i = 1; while(canContinue) { Result> result = utxoService.getUtxos(address, getUtxoFetchSize(), @@ -49,6 +52,9 @@ public List selectUtxos(String address, String unit, BigInteger amount, Se if(excludeUtxos.contains(utxo)) continue; + if(utxo.getDataHash() != null && !utxo.getDataHash().isEmpty() && ignoreUtxosWithDatumHash()) + continue; + List utxoAmounts = utxo.getAmount(); boolean unitFound = false; @@ -76,6 +82,16 @@ public List selectUtxos(String address, String unit, BigInteger amount, Se return selectedUtxos; } + @Override + public boolean ignoreUtxosWithDatumHash() { + return ignoreUtxosWithDatumHash; + } + + @Override + public void setIgnoreUtxosWithDatumHash(boolean ignoreUtxosWithDatumHash) { + this.ignoreUtxosWithDatumHash = ignoreUtxosWithDatumHash; + } + protected List filter(List fetchData) { return fetchData; } diff --git a/src/main/java/com/bloxbean/cardano/client/backend/model/Utxo.java b/src/main/java/com/bloxbean/cardano/client/backend/model/Utxo.java index b99bd06c..f212070f 100644 --- a/src/main/java/com/bloxbean/cardano/client/backend/model/Utxo.java +++ b/src/main/java/com/bloxbean/cardano/client/backend/model/Utxo.java @@ -19,9 +19,9 @@ @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) public class Utxo { private String txHash; -// private int txIndex; private int outputIndex; private List amount; + private String dataHash; @Override public boolean equals(Object o) { diff --git a/src/test/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImplTest.java b/src/test/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImplTest.java new file mode 100644 index 00000000..6e4773f2 --- /dev/null +++ b/src/test/java/com/bloxbean/cardano/client/backend/api/helper/impl/DefaultUtxoSelectionStrategyImplTest.java @@ -0,0 +1,85 @@ +package com.bloxbean.cardano.client.backend.api.helper.impl; + +import com.bloxbean.cardano.client.backend.api.UtxoService; +import com.bloxbean.cardano.client.backend.exception.ApiException; +import com.bloxbean.cardano.client.backend.model.Result; +import com.bloxbean.cardano.client.backend.model.Utxo; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.bloxbean.cardano.client.common.CardanoConstants.LOVELACE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class DefaultUtxoSelectionStrategyImplTest { + + private static final String LIST_1 = "list1"; + @Mock + UtxoService utxoService; + + ObjectMapper objectMapper = new ObjectMapper(); + + String dataFile = "utxos-selection-strategy.json"; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + } + + private List loadUtxos(String key) throws IOException { + TypeReference>> typeRef + = new TypeReference>>() {}; + Map> map = objectMapper.readValue(this.getClass().getClassLoader().getResourceAsStream(dataFile), typeRef); + return map.getOrDefault(key, Collections.emptyList()); + } + + @Test + void selectUtxos_returnUtxosWithoutDataHashWhenIgnoreUtxoDataHashIsTrue() throws IOException, ApiException { + String address = "addr_test1qqwpl7h3g84mhr36wpetk904p7fchx2vst0z696lxk8ujsjyruqwmlsm344gfux3nsj6njyzj3ppvrqtt36cp9xyydzqzumz82"; + + List utxos = loadUtxos(LIST_1); + given(utxoService.getUtxos(any(), anyInt(), anyInt(), any())).willReturn(Result.success(utxos.toString()).withValue(utxos).code(200)); + + DefaultUtxoSelectionStrategyImpl selectionStrategy = new DefaultUtxoSelectionStrategyImpl(utxoService); + List selectedUtxos = selectionStrategy.selectUtxos(address, LOVELACE, new BigInteger("5000"), Collections.EMPTY_SET); + + List txnHashList = selectedUtxos.stream().map(utxo -> utxo.getTxHash()).collect(Collectors.toList()); + + assertThat(txnHashList).doesNotContain("88c014d348bf1919c78a5cb87a5beed87729ff3f8a2019be040117a41a83e82e"); + assertThat(txnHashList).hasSize(3); + } + + @Test + void selectUtxos_returnUtxosWithoutDataHashWhenIgnoreUtxoDataHashIsFalse() throws IOException, ApiException { + String address = "addr_test1qqwpl7h3g84mhr36wpetk904p7fchx2vst0z696lxk8ujsjyruqwmlsm344gfux3nsj6njyzj3ppvrqtt36cp9xyydzqzumz82"; + + List utxos = loadUtxos(LIST_1); + given(utxoService.getUtxos(any(), anyInt(), anyInt(), any())).willReturn(Result.success(utxos.toString()).withValue(utxos).code(200)); + + DefaultUtxoSelectionStrategyImpl selectionStrategy = new DefaultUtxoSelectionStrategyImpl(utxoService); + selectionStrategy.setIgnoreUtxosWithDatumHash(false); + + List selectedUtxos = selectionStrategy.selectUtxos(address, LOVELACE, new BigInteger("5000"), Collections.EMPTY_SET); + + List txnHashList = selectedUtxos.stream().map(utxo -> utxo.getTxHash()).collect(Collectors.toList()); + + assertThat(txnHashList).contains("88c014d348bf1919c78a5cb87a5beed87729ff3f8a2019be040117a41a83e82e"); + assertThat(txnHashList).hasSize(3); + } +} diff --git a/src/test/resources/utxos-selection-strategy.json b/src/test/resources/utxos-selection-strategy.json new file mode 100644 index 00000000..e374e256 --- /dev/null +++ b/src/test/resources/utxos-selection-strategy.json @@ -0,0 +1,58 @@ +{ + "list1": [ + { + "tx_hash": "735262c68b5fa220dee2b447d0d1dd44e0800ba6212dcea7955c561f365fb0e9", + "tx_index": 0, + "output_index": 0, + "data_hash": null, + "amount": [ + { + "unit": "lovelace", + "quantity": "1000" + }, + { + "unit": "6b8d07d69639e9413dd637a1a815a7323c69c86abbafb66dbfdb1aa7", + "quantity": "2" + } + ], + "block": "656f65cb141fa8a37b50c60da1e9f28a2e45a1ea80e13ac4f9e1323a2ec07557" + }, + { + "tx_hash": "88c014d348bf1919c78a5cb87a5beed87729ff3f8a2019be040117a41a83e82e", + "tx_index": 1, + "output_index": 1, + "data_hash": "somehashxxxxdfksdjfsdfsldfjslfjslfjslfjslfsdlfsdf", + "amount": [ + { + "unit": "lovelace", + "quantity": "2000" + } + ], + "block": "1a06a27cfcb9316dfc3a498e96b80a3a9427ad03170077e3a80fd41a43ec3d7b" + }, + { + "tx_hash": "6674e44f0f03915b1611ce58aaff5f2e52054e1911fbcd0f17dbc205f44763b6", + "tx_index": 0, + "output_index": 0, + "amount": [ + { + "unit": "lovelace", + "quantity": "3000" + } + ], + "block": "eb2043df94b34d1746cb47303446237e6dca7d051100c8f4229795be8c1f5e63" + }, + { + "tx_hash": "a712906ae823ecefe6cab76e5cfd427bd0b6144df10c6f89a56fbdf30fa807f4", + "tx_index": 1, + "output_index": 1, + "amount": [ + { + "unit": "lovelace", + "quantity": "4000" + } + ], + "block": "c2a5408a54596d52e3b0d7aed546ceb7e4d74a06ce80dca13664acd3afdea3e5" + } + ] +}