From 573001d49ad985230079137b96f0ce99b4c240f1 Mon Sep 17 00:00:00 2001 From: Andrey Bronin Date: Tue, 17 Jun 2025 13:18:53 +0300 Subject: [PATCH] lower bound detector for receipts (#674) * lower bound detector for receipts * Add more non-retryable errors to dshackle proof detection --- emerald-grpc | 2 +- .../dshackle/rpc/ChainEventMapper.kt | 1 + .../io/emeraldpay/dshackle/rpc/StreamHead.kt | 1 + .../EthereumLowerBoundProofDetector.kt | 2 + .../EthereumLowerBoundReceiptsDetector.kt | 76 +++++++++++++++++++ .../ethereum/EthereumLowerBoundService.kt | 1 + .../upstream/lowerbound/LowerBoundData.kt | 3 +- 7 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundReceiptsDetector.kt diff --git a/emerald-grpc b/emerald-grpc index 3b46c590..6ca43f4d 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 3b46c590db6a631fcb6ed4a4224678dae2586ebd +Subproject commit 6ca43f4dc268ac60b50d44f45b8715094be6c6fd diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt index 85727e89..d193a4ed 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt @@ -150,6 +150,7 @@ class ChainEventMapper { LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH + LowerBoundType.RECEIPTS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt index 8d1a71ed..f72d75d2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt @@ -110,6 +110,7 @@ class StreamHead( LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH + LowerBoundType.RECEIPTS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundProofDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundProofDetector.kt index 36934c4d..67bf7c90 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundProofDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundProofDetector.kt @@ -26,6 +26,8 @@ class EthereumLowerBoundProofDetector( "proofs are available only for the 'latest' block", "missing trie node", "cannot find EVM IAVL store", + "old data not available due to pruning", + "no historical RPC is available", ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundReceiptsDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundReceiptsDetector.kt new file mode 100644 index 00000000..58f478d7 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundReceiptsDetector.kt @@ -0,0 +1,76 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import io.emeraldpay.dshackle.Defaults +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundData +import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundDetector +import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType +import io.emeraldpay.dshackle.upstream.lowerbound.detector.RecursiveLowerBound +import io.emeraldpay.dshackle.upstream.lowerbound.toHex +import io.emeraldpay.dshackle.upstream.rpcclient.ListParams +import reactor.core.publisher.Flux + +class EthereumLowerBoundReceiptsDetector( + private val upstream: Upstream, +) : LowerBoundDetector(upstream.getChain()) { + + companion object { + const val MAX_OFFSET = 20 + private const val NO_RECEIPTS_DATA = "No receipts data" + + private val NO_RECEIPTS_ERRORS = setOf( + NO_RECEIPTS_DATA, + "block not found with number", + "requested epoch was a null round", + "missing trie node", + "old data not available due to pruning", + ).plus(EthereumLowerBoundBlockDetector.NO_BLOCK_ERRORS) + } + + private val recursiveLowerBound = RecursiveLowerBound(upstream, LowerBoundType.RECEIPTS, NO_RECEIPTS_ERRORS, lowerBounds) + + override fun period(): Long { + return 3 + } + + override fun internalDetectLowerBound(): Flux { + return recursiveLowerBound.recursiveDetectLowerBoundWithOffset(MAX_OFFSET) { block -> + upstream.getIngressReader() + .read( + ChainRequest("eth_getBlockByNumber", ListParams(block.toHex(), false)), + ) + .timeout(Defaults.internalCallsTimeout) + .doOnNext { + if (it.hasResult() && it.getResult().contentEquals("null".toByteArray())) { + throw IllegalStateException(NO_RECEIPTS_DATA) + } + } + .handle { it, sink -> + val blockJson = BlockContainer.fromEthereumJson(it.getResult(), upstream.getId()) + if (blockJson.transactions.isEmpty()) { + sink.error(IllegalStateException(NO_RECEIPTS_DATA)) + return@handle + } + sink.next(blockJson.transactions[0].toHexWithPrefix()) + } + .flatMap { tx -> + upstream.getIngressReader() + .read( + ChainRequest("eth_getTransactionReceipt", ListParams(tx)), + ) + .timeout(Defaults.internalCallsTimeout) + .doOnNext { + if (it.hasResult() && it.getResult().contentEquals("null".toByteArray())) { + throw IllegalStateException(NO_RECEIPTS_DATA) + } + } + } + } + } + + override fun types(): Set { + return setOf(LowerBoundType.RECEIPTS) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundService.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundService.kt index 1c30203b..23a7cb6f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundService.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundService.kt @@ -15,6 +15,7 @@ class EthereumLowerBoundService( EthereumLowerBoundBlockDetector(upstream), EthereumLowerBoundTxDetector(upstream), EthereumLowerBoundProofDetector(upstream), + EthereumLowerBoundReceiptsDetector(upstream), ) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt index 333ff1a3..20ccc7a0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt @@ -20,7 +20,7 @@ data class LowerBoundData( } enum class LowerBoundType { - UNKNOWN, STATE, SLOT, BLOCK, TX, LOGS, TRACE, PROOF, BLOB, EPOCH + UNKNOWN, STATE, SLOT, BLOCK, TX, LOGS, TRACE, PROOF, BLOB, EPOCH, RECEIPTS } fun BlockchainOuterClass.LowerBoundType.fromProtoType(): LowerBoundType { @@ -36,5 +36,6 @@ fun BlockchainOuterClass.LowerBoundType.fromProtoType(): LowerBoundType { BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF -> LowerBoundType.PROOF BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB -> LowerBoundType.BLOB BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH -> LowerBoundType.EPOCH + BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS -> LowerBoundType.RECEIPTS } }