lower bound detector for receipts (#674)

* lower bound detector for receipts

* Add more non-retryable errors to dshackle proof detection
This commit is contained in:
Andrey Bronin
2025-06-17 13:18:53 +03:00
committed by GitHub
parent 51719ee979
commit 573001d49a
7 changed files with 84 additions and 2 deletions

View File

@@ -150,6 +150,7 @@ class ChainEventMapper {
LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF
LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB
LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH
LowerBoundType.RECEIPTS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS
} }
} }
} }

View File

@@ -110,6 +110,7 @@ class StreamHead(
LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF LowerBoundType.PROOF -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_PROOF
LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB LowerBoundType.BLOB -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB
LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH LowerBoundType.EPOCH -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH
LowerBoundType.RECEIPTS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS
} }
} }

View File

@@ -26,6 +26,8 @@ class EthereumLowerBoundProofDetector(
"proofs are available only for the 'latest' block", "proofs are available only for the 'latest' block",
"missing trie node", "missing trie node",
"cannot find EVM IAVL store", "cannot find EVM IAVL store",
"old data not available due to pruning",
"no historical RPC is available",
) )
} }

View File

@@ -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<LowerBoundData> {
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<LowerBoundType> {
return setOf(LowerBoundType.RECEIPTS)
}
}

View File

@@ -15,6 +15,7 @@ class EthereumLowerBoundService(
EthereumLowerBoundBlockDetector(upstream), EthereumLowerBoundBlockDetector(upstream),
EthereumLowerBoundTxDetector(upstream), EthereumLowerBoundTxDetector(upstream),
EthereumLowerBoundProofDetector(upstream), EthereumLowerBoundProofDetector(upstream),
EthereumLowerBoundReceiptsDetector(upstream),
) )
} }
} }

View File

@@ -20,7 +20,7 @@ data class LowerBoundData(
} }
enum class LowerBoundType { 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 { 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_PROOF -> LowerBoundType.PROOF
BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB -> LowerBoundType.BLOB BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOB -> LowerBoundType.BLOB
BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH -> LowerBoundType.EPOCH BlockchainOuterClass.LowerBoundType.LOWER_BOUND_EPOCH -> LowerBoundType.EPOCH
BlockchainOuterClass.LowerBoundType.LOWER_BOUND_RECEIPTS -> LowerBoundType.RECEIPTS
} }
} }