diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/AuxDataSerializer.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/AuxDataSerializer.java index 912c651..727a28f 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/AuxDataSerializer.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/AuxDataSerializer.java @@ -5,7 +5,6 @@ import com.bloxbean.cardano.client.exception.CborRuntimeException; import com.bloxbean.cardano.client.metadata.Metadata; import com.bloxbean.cardano.client.metadata.cbor.CBORMetadata; -import com.bloxbean.cardano.client.metadata.helper.MetadataToJsonNoSchemaConverter; import com.bloxbean.cardano.client.transaction.spec.AuxiliaryData; import com.bloxbean.cardano.yaci.core.model.AuxData; import com.bloxbean.cardano.yaci.core.model.NativeScript; diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/MetadataToJsonNoSchemaConverter.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/MetadataToJsonNoSchemaConverter.java new file mode 100644 index 0000000..ab63a0c --- /dev/null +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/MetadataToJsonNoSchemaConverter.java @@ -0,0 +1,133 @@ +package com.bloxbean.cardano.yaci.core.model.serializers; + +import co.nstant.in.cbor.CborDecoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.*; +import com.bloxbean.cardano.client.exception.CborDeserializationException; +import com.bloxbean.cardano.client.metadata.exception.MetadataDeSerializationException; +import com.bloxbean.cardano.client.util.HexUtil; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import static co.nstant.in.cbor.model.MajorType.*; + +//TODO -- Remove once it's fixed in CCL (https://github.com/cardano-foundation/cf-ledger-sync/issues/143) +class MetadataToJsonNoSchemaConverter { + private final static ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Convert cbor metadata bytes to json string + * @param cborBytes + * @return + */ + public static String cborBytesToJson(byte[] cborBytes) { + try { + return cborHexToJson(HexUtil.encodeHexString(cborBytes)); + } catch (Exception e) { + throw new MetadataDeSerializationException("Deserialization error", e); + } + } + + /** + * Converts cbor metadata bytes in hex format to json string + * @param hex + * @return + */ + public static String cborHexToJson(String hex) { + try { + java.util.Map result = cborHexToJavaMap(hex); + + return objectMapper.writeValueAsString(result); + } catch (Exception e) { + throw new MetadataDeSerializationException("Deserialization error", e); + } + } + + private static java.util.Map cborHexToJavaMap(String hex) throws CborDeserializationException { + byte[] cborBytes = HexUtil.decodeHexString(hex); + List dataItemList = null; + try { + dataItemList = CborDecoder.decode(cborBytes); + } catch (CborException e) { + throw new CborDeserializationException("Cbor deserialization failed", e); + } + + if(dataItemList != null && dataItemList.size() > 1) + throw new MetadataDeSerializationException("Multiple DataItems found at top level. Should be zero : " + dataItemList.size()); + + java.util.Map result = new HashMap(); + DataItem dataItem = dataItemList.get(0); + if(dataItem instanceof Map) { + result = processMap((Map)dataItem); + } else { + throw new MetadataDeSerializationException("Top leve object should be a Map : " + dataItem.getMajorType().toString()); + } + return result; + } + + private static java.util.Map processMap(Map map) { + java.util.Map resultMap = new HashMap(); + Collection keys = map.getKeys(); + for(DataItem keyItem: keys) { + DataItem valueItem = map.get(keyItem); + Object key = processKey(keyItem); + Object value = processValue(valueItem); + + resultMap.put(key, value); + } + return resultMap; + } + + private static Object processKey(DataItem keyItem) { + if (UNSIGNED_INTEGER.equals(keyItem.getMajorType())){ + return ((UnsignedInteger) keyItem).getValue(); + } else if(NEGATIVE_INTEGER.equals(keyItem.getMajorType())) { + return ((NegativeInteger) keyItem).getValue(); + } else if (BYTE_STRING.equals(keyItem.getMajorType())) { + byte[] bytes = ((ByteString) keyItem).getBytes(); + return "0x" + HexUtil.encodeHexString(bytes); + } else if (UNICODE_STRING.equals(keyItem.getMajorType())) { + return ((UnicodeString) keyItem).getString(); + } else if(MAP.equals(keyItem.getMajorType())){ + return processMap((Map)keyItem); + } else if(ARRAY.equals(keyItem.getMajorType())) { + return processArray((Array)keyItem); + } else { + throw new MetadataDeSerializationException("Invalid key type : " + keyItem.getMajorType()); + } + } + + private static Object processValue(DataItem valueItem) { + if(UNSIGNED_INTEGER.equals(valueItem.getMajorType())){ + return ((UnsignedInteger)valueItem).getValue(); + } else if(NEGATIVE_INTEGER.equals(valueItem.getMajorType())) { + return ((NegativeInteger)valueItem).getValue(); + } else if(BYTE_STRING.equals(valueItem.getMajorType())) { + byte[] bytes = ((ByteString)valueItem).getBytes(); + return "0x" + HexUtil.encodeHexString(bytes); + } else if(UNICODE_STRING.equals(valueItem.getMajorType())) { + return ((UnicodeString)valueItem).getString(); + } else if(MAP.equals(valueItem.getMajorType())){ + return processMap((Map)valueItem); + } else if(ARRAY.equals(valueItem.getMajorType())) { + return processArray((Array)valueItem); + } else { + throw new MetadataDeSerializationException("Unsupported type : " + valueItem.getMajorType()); + } + } + + private static Object processArray(Array array) { + List dataItems = array.getDataItems(); + List resultList = new ArrayList(); + for(DataItem valueItem: dataItems) { + Object valueObj = processValue(valueItem); + resultList.add(valueObj); + } + return resultList; + } + +} diff --git a/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockFetcherIT.java b/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockFetcherIT.java index 117491a..7386233 100644 --- a/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockFetcherIT.java +++ b/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockFetcherIT.java @@ -229,6 +229,36 @@ public void blockFound(Block block) { assertThat(amounts).hasSize(0); } + @Test + public void fetchBlock_metadataWithArrayKey() throws InterruptedException { + VersionTable versionTable = N2NVersionTableConstant.v4AndAbove(protocolMagic); + BlockFetcher blockFetcher = new BlockFetcher(node, nodePort, versionTable); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + StringBuilder sb = new StringBuilder(); + blockFetcher.addBlockFetchListener(new BlockfetchAgentListener() { + @Override + public void blockFound(Block block) { + var metatdataJson = block.getAuxiliaryDataMap().get(2).getMetadataJson(); + System.out.println(metatdataJson); + sb.append(metatdataJson); + countDownLatch.countDown(); + } + }); + blockFetcher.start(); + + Point from = new Point(54478325, "2cbc2a95ed9ab49b10d9016cc0201c19749c79ba28c9b55ff70860e65d67d077"); + Point to = new Point(54478325, "2cbc2a95ed9ab49b10d9016cc0201c19749c79ba28c9b55ff70860e65d67d077"); + + blockFetcher.fetch(from, to); + + countDownLatch.await(10, TimeUnit.SECONDS); + blockFetcher.shutdown(); + + assertThat(sb.toString()).isNotEqualTo("null"); + } + /** Not able to fetch block 0 @Test public void fetchGenesisBlockByron() throws InterruptedException {