diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/exception/BlockParseRuntimeException.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/exception/BlockParseRuntimeException.java new file mode 100644 index 00000000..642747b0 --- /dev/null +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/exception/BlockParseRuntimeException.java @@ -0,0 +1,26 @@ +package com.bloxbean.cardano.yaci.core.exception; + +public class BlockParseRuntimeException extends RuntimeException { + private Long blockNumber; + private byte[] blockCbor; + + public BlockParseRuntimeException(Long blockNumber, byte[] blockCbor, Exception e) { + super(e); + this.blockNumber = blockNumber; + this.blockCbor = blockCbor; + } + + public BlockParseRuntimeException(Long blockNumber, byte[] blockCbor, String msg, Exception e) { + super(msg, e); + this.blockNumber = blockNumber; + this.blockCbor = blockCbor; + } + + public Long getBlockNumber() { + return blockNumber; + } + + public byte[] getBlockCbor() { + return blockCbor; + } +} diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgent.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgent.java index 31fbd7ec..6e8eafaf 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgent.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgent.java @@ -4,6 +4,7 @@ import co.nstant.in.cbor.model.UnsignedInteger; import com.bloxbean.cardano.yaci.core.common.EraUtil; import com.bloxbean.cardano.yaci.core.common.GenesisConfig; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.Block; import com.bloxbean.cardano.yaci.core.model.BlockHeader; import com.bloxbean.cardano.yaci.core.model.Era; @@ -138,16 +139,19 @@ private void onReceiveBlocks(MsgBlock message) { errorBlks++; log.error("Error in parsing", e); + Long blockNumber = null; //Catch exception to avoid exception propagation try { Array headerArray = (Array) ((Array) array.getDataItems().get(1)).getDataItems().get(0); BlockHeader blockHeader = BlockHeaderSerializer.INSTANCE.getBlockHeaderFromHeaderArray(headerArray); log.error("BlockHeader >> Block No: " + blockHeader.getHeaderBody().getBlockNumber() + ", Slot: " + blockHeader.getHeaderBody().getSlot()); + blockNumber = blockHeader.getHeaderBody().getBlockNumber(); } catch (Exception e1) { log.error("Error in parsing block header", e1); } - getAgentListeners().stream().forEach(blockfetchAgentListener -> blockfetchAgentListener.onParsingError(e)); + var blockParseException = new BlockParseRuntimeException(blockNumber, body, e); + getAgentListeners().stream().forEach(blockfetchAgentListener -> blockfetchAgentListener.onParsingError(blockParseException)); } } diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentListener.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentListener.java index ea3dfa8a..3196476d 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentListener.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentListener.java @@ -1,5 +1,6 @@ package com.bloxbean.cardano.yaci.core.protocol.blockfetch; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.Block; import com.bloxbean.cardano.yaci.core.model.byron.ByronEbBlock; import com.bloxbean.cardano.yaci.core.model.byron.ByronMainBlock; @@ -35,7 +36,7 @@ default void byronEbBlockFound(ByronEbBlock byronEbBlock) { } - default void onParsingError(Exception e) { + default void onParsingError(BlockParseRuntimeException e) { } diff --git a/core/src/test/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentTest.java b/core/src/test/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentTest.java new file mode 100644 index 00000000..fe794c99 --- /dev/null +++ b/core/src/test/java/com/bloxbean/cardano/yaci/core/protocol/blockfetch/BlockfetchAgentTest.java @@ -0,0 +1,45 @@ +package com.bloxbean.cardano.yaci.core.protocol.blockfetch; + +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.UnsignedInteger; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; +import com.bloxbean.cardano.yaci.core.protocol.blockfetch.messages.MsgBlock; +import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil; +import com.bloxbean.cardano.yaci.core.util.HexUtil; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +public class BlockfetchAgentTest { + + @Test + void testParseErrorHandling() { + BlockfetchAgent blockfetchAgent = new BlockfetchAgent(); + BlockfetchAgentListener mockListener = mock(BlockfetchAgentListener.class); + blockfetchAgent.addListener(mockListener); + + //Block cbor with unparsable data + String cborHex = "820785828a1a00325c341a04e93d335820616ba2c1b29f32f69408f024aa43577ea0db22a37d023667028f46c6bf6a9bd25820e9e9e0eac579ac6c36f23f183373e6259ba4e01bb4312c648987640e1dcf22ff5820cf68024875af973ef111b33d896a4072814215cd4454bfcf8d08440be55ec81c825840fc00bbd0f5c509684691cd33d3d084d82f63a2d09ce9f45ae219446c7e13fcb487958bfcbe5bd28fca52aa18462f1c4c7c043f4f416182c756749887ce5a18b95850caed967b60ad049456260fa42c4e5af06401be312b38af21f7437abaa6f0ad88503a68bc505e83633c39473121ce8aa4463589ca1077f8d372b4e3f9de2f3724fda5201de45f1e17e070709fc62d3805190374582070a576367e8132e4e5a3c2ab0e647494fb80a3168b207c401c558d721f81069d8458203e5bc618a0d992bc8fba5276e51dd5d1ca2837a3799a6b10b0c1c560c90f28070a19023f58409c80a80b63829376aa475f284033b30fc52e23d4a5a6c3062f82018ec69b58c15c2b549b9250b2e546bb83ef7049e5b228c0b676de89e00296d48aee95af5302820a025901c07a7c704ff4bade83915c094d4c18a93e05c6e14c32bc4a50a76648f1c779b4646a7fcf8e3b5cd8a4ae1270f988d9049bfea8b42d6e30890c65fb4af7a5a92c07a817e9247c4d30fc9f7072238cb9ceee6bcd4065d71285f5e14b47dc1f8ead1981365f853844000a4c3b881b14505f4634355d5960b8b86db52f797e910af8a3f5c9f5caa1acc20e8c6532d21f7973da52b0b46b7bc880198e36b144d177876eeac0b217b0e62fc62641a7280a00e96538f28be88644aa137004e3ec75e8a9904fb4c8424fa3bcd7941dc6b3d13efa53b507484c4bfc7895f576e35944182aaac2ed645994a0d05a7b1c9a953279e99144ebc0295d2c2b656378f8d61f043e866cbfd33fe4f36021f3f718136516e28ecb9981cba008601b7d75084dde0ba6df854a860d3cac659def796ad63440a644ab4e46db9c650f6f4161f85c0696aada2edb2466af971213fc607dc72c20fc95cd1efa9a8fac19308f40b0a27608ca23ac4a9215704901eda739c3c2f66f1cdbc5ea2a204ad9154e9fa1c139aaa6152d734e4a521a7c7813614666ecbadad77aed3f1e67d2272c44e56534bc17521b6406ee4c359ec698e7006b91ffb896ad5ce2e9a21d0c6450499d71cc1da73c0cc681a800d9010281825820deee1236f2468a916e525d952996944621a762d921e2ac9e518fb42166e63d7101018282583900ff76e52a2bbd75feedecd082d9e87a4b3c2232a048898570c24b747871bc22b9a36f8b31472a273fd7ae610ccaabf15117b92bc97497b7e4821a001e8480a1581c23b62b4570c332e3b1809370805286f14824437ed661e09fd1037619a1495061796c4b6f796e310182583900b08760eb6576fcd70e653e61a26a8a0b5180e403175e328db577c9600416e08ece7a5d20ea5c141163223ad8aa86ce604f1b82e96d891bae1a050ab9ce021a00034cd5031a04e940a90758206baaa832cd21bc539f0643f3e6257a4e21ce1a548d1e9b18be17516d712d6ffe081a04e93cc109a1581c23b62b4570c332e3b1809370805286f14824437ed661e09fd1037619a1495061796c4b6f796e31010ed9010281581c6d66c78fb7803aca916c571ea3180f470a830919abf354fbada766b781a200d9010282825820cb05a31a8c6a27c96c9b777d332a43d75c8a0154c560492f814d9375d8dd1bcf584045bfd6b0e9e370cfae5ab005e5844bfed36c91e6262dbd0d78fd25a219a6f65534dcece75fd954dfcde86f9fe933bed75488d42a28286bf0579a32de7e75980a82582043f21b577a7c12ed60d99d981f3af0822ead911970836a56b31426caec3c16c558401858abe89432588bc6518a4f8cc7f758f7b4cf6999ca340cf2f7fda67b63735694a42e1d5d5dad7871b54e3d6cedc758ef2b1061135aeb6312341e02554a5d0f01d901028182019f8200581c6d66c78fb7803aca916c571ea3180f470a830919abf354fbada766b782051a04f87c6effa100d90103a100a11902d1a178383463303937313566346539316462393262323234376635653561636462306138663037656161376334303230376264383234626564336632a172353036313739364334423646373936453331a8646e616d656b5061796c4b6f796e20233165696d61676582784061646166733a2f2f6132373034303239663331316665356566366239303463643233363536306534626438643666306633353430316162316331356464333130683366336166653066696d656469615479706569696d6167652f706e676a6261636b67726f756e6466526f636b657264626f64796442617365676c696e65617274644261736568636c6f7468696e67645079726f6465796573645a65737480"; + byte[] cborBytes = HexUtil.decodeHexString(cborHex); + + Array di = (Array) CborSerializationUtil.deserializeOne(cborBytes); + + var diList = di.getDataItems(); + //Add a random number to witnesset to create invalid cbor + ((Array)((Array)diList.get(1)).getDataItems().get(2)).getDataItems().add(new UnsignedInteger(8999)); + + //Updated cbor bytes after modification + cborBytes = CborSerializationUtil.serialize(di); + + // Create a mocked MsgBlock with corrupted data + MsgBlock corruptedMsgBlock = mock(MsgBlock.class); + when(corruptedMsgBlock.getBytes()).thenReturn(cborBytes); + + // Act + blockfetchAgent.processResponse(corruptedMsgBlock); + + verify(mockListener, times(1)).onParsingError(any(BlockParseRuntimeException.class)); + } +} diff --git a/gradle.properties b/gradle.properties index 00404bab..0c6c2235 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group = com.bloxbean.cardano artifactId = yaci -version = 0.4.0-beta3 +version = 0.4.0-beta4 diff --git a/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockSyncIT.java b/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockSyncIT.java index 20e020d2..599f7235 100644 --- a/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockSyncIT.java +++ b/helper/src/integrationTest/java/com/bloxbean/cardano/yaci/helper/BlockSyncIT.java @@ -1,10 +1,12 @@ package com.bloxbean.cardano.yaci.helper; import com.bloxbean.cardano.yaci.core.common.Constants; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.Block; import com.bloxbean.cardano.yaci.core.model.Era; import com.bloxbean.cardano.yaci.core.protocol.chainsync.messages.Point; import com.bloxbean.cardano.yaci.core.protocol.chainsync.messages.Tip; +import com.bloxbean.cardano.yaci.core.util.HexUtil; import com.bloxbean.cardano.yaci.helper.listener.BlockChainDataListener; import com.bloxbean.cardano.yaci.helper.model.Transaction; import org.junit.jupiter.api.Disabled; @@ -136,4 +138,31 @@ public void intersactFound(Tip tip, Point point) { countDownLatch.await(60, TimeUnit.SECONDS); assertThat(success.get()).isTrue(); } + + @Test + void syncFromPoint_continueOnParseError() throws InterruptedException { + BlockSync blockSync = new BlockSync(Constants.PREVIEW_PUBLIC_RELAY_ADDR, Constants.PREPROD_PUBLIC_RELAY_PORT, + Constants.PREVIEW_PROTOCOL_MAGIC, Constants.WELL_KNOWN_PREVIEW_POINT); + + AtomicLong blockNo = new AtomicLong(); + CountDownLatch countDownLatch = new CountDownLatch(10); + blockSync.startSync(new Point(82394373, "49033a53f777ce932be005539b01ef4d7e3b49ee4ae2f315342a182f3281384a"), + new BlockChainDataListener() { + @Override + public void onBlock(Era era, Block block, List transactions) { + System.out.println(block.getHeader().getHeaderBody().getBlockNumber()); + blockNo.set(block.getHeader().getHeaderBody().getBlockNumber()); + countDownLatch.countDown(); + } + + @Override + public void onParsingError(BlockParseRuntimeException e) { + System.out.println("ERROR BLock: " + e.getBlockNumber()); + System.out.println("CBOR: " + HexUtil.encodeHexString(e.getBlockCbor())); + } + }); + + countDownLatch.await(60, TimeUnit.SECONDS); + assertThat(blockNo.get()).isGreaterThan(3300404 + 5); + } } diff --git a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcher.java b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcher.java index a812658a..ce995d54 100644 --- a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcher.java +++ b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcher.java @@ -1,6 +1,7 @@ package com.bloxbean.cardano.yaci.helper; import com.bloxbean.cardano.yaci.core.common.Constants; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.Block; import com.bloxbean.cardano.yaci.core.model.BlockHeader; import com.bloxbean.cardano.yaci.core.model.byron.ByronBlockHead; @@ -194,6 +195,11 @@ public void byronBlockFound(ByronMainBlock byronBlock) { public void byronEbBlockFound(ByronEbBlock byronEbBlock) { chainSyncAgent.sendNextMessage(); } + + @Override + public void onParsingError(BlockParseRuntimeException e) { + chainSyncAgent.sendNextMessage(); + } }); keepAliveAgent.addListener(response -> { diff --git a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockChainDataListener.java b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockChainDataListener.java index e85a3c6e..c4ef95f3 100644 --- a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockChainDataListener.java +++ b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockChainDataListener.java @@ -1,5 +1,6 @@ package com.bloxbean.cardano.yaci.helper.listener; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.Block; import com.bloxbean.cardano.yaci.core.model.Era; import com.bloxbean.cardano.yaci.core.model.byron.ByronEbBlock; @@ -43,5 +44,5 @@ default void intersactNotFound(Tip tip) {} default void onDisconnect() {} - default void onParsingError(Exception e) {} + default void onParsingError(BlockParseRuntimeException e) {} } diff --git a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockFetchAgentListenerAdapter.java b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockFetchAgentListenerAdapter.java index 2e2cf911..aec42350 100644 --- a/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockFetchAgentListenerAdapter.java +++ b/helper/src/main/java/com/bloxbean/cardano/yaci/helper/listener/BlockFetchAgentListenerAdapter.java @@ -1,5 +1,6 @@ package com.bloxbean.cardano.yaci.helper.listener; +import com.bloxbean.cardano.yaci.core.exception.BlockParseRuntimeException; import com.bloxbean.cardano.yaci.core.model.*; import com.bloxbean.cardano.yaci.core.model.byron.ByronEbBlock; import com.bloxbean.cardano.yaci.core.model.byron.ByronMainBlock; @@ -133,7 +134,7 @@ public void onDisconnect() { } @Override - public void onParsingError(Exception e) { + public void onParsingError(BlockParseRuntimeException e) { blockChainDataListener.onParsingError(e); } } diff --git a/helper/src/test/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcherIT.java b/helper/src/test/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcherIT.java index 39304ccb..74f285ad 100644 --- a/helper/src/test/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcherIT.java +++ b/helper/src/test/java/com/bloxbean/cardano/yaci/helper/N2NChainSyncFetcherIT.java @@ -81,4 +81,28 @@ public void blockFound(Block block) { System.out.println(blocks.get(0)); assertThat(blocks.get(0).getHeader().getHeaderBody().getBlockNumber()).isGreaterThan(287480); } + + @Test + void testChainSync_continueOnParseError() throws InterruptedException { + N2NChainSyncFetcher chainSyncFetcher = new N2NChainSyncFetcher(Constants.PREVIEW_PUBLIC_RELAY_ADDR, + Constants.PREVIEW_PUBLIC_RELAY_PORT, + new Point(82394373, "49033a53f777ce932be005539b01ef4d7e3b49ee4ae2f315342a182f3281384a"), + Constants.PREVIEW_PROTOCOL_MAGIC, false); + + List blocks = new ArrayList<>(); + CountDownLatch countDownLatch = new CountDownLatch(10); + chainSyncFetcher.addBlockFetchListener(new BlockfetchAgentListener() { + @Override + public void blockFound(Block block) { + blocks.add(block); + countDownLatch.countDown(); + } + }); + chainSyncFetcher.start(); + countDownLatch.await(60, TimeUnit.SECONDS); + chainSyncFetcher.shutdown(); + + System.out.println(blocks.get(0)); + assertThat(blocks).hasSizeGreaterThan(8); + } }