Logs subscription via eth_getLogs (#356)

move retrieving logs subscription to eth_getLogs by hash
This commit is contained in:
Vyacheslav
2023-12-04 17:09:32 +02:00
committed by GitHub
parent 9696f06732
commit 0a47da602a
14 changed files with 157 additions and 769 deletions

View File

@@ -1,185 +0,0 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.test.ApiReaderMock
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.AbstractHead
import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.Lifecycle
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.etherjar.domain.BlockHash
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.Sinks
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
class EnrichedMergedHeadSpec extends Specification {
def "ensures that heads are running on start"() {
setup:
def head1 = Mock(TestHead) {
_ * isRunning() >> false
_ * getFlux() >> Flux.empty()
}
def head2 = Mock(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.empty()
}
def head3 = Mock(TestHead) {
_ * isRunning() >> false
_ * getFlux() >> Flux.empty()
}
def api = new ApiReaderMock()
when:
def merged = new EnrichedMergedHead([head1, head2], head3, Schedulers.boundedElastic(), new BlockReader(api))
merged.start()
then:
1 * head3.start()
1 * head1.start()
merged.isRunning()
}
def "if enriched block comes from reference head we pass it along instantly"() {
setup:
def block = TestingCommons.enrichedBlockForEthereum(100)
def api = new ApiReaderMock()
def head = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.just(block)
}
when:
def merge = new EnrichedMergedHead([], head, Schedulers.boundedElastic(), new BlockReader(api))
then:
StepVerifier.create(merge.getFlux())
.then { merge.start() }
.expectNext(block)
.thenCancel()
.verify(Duration.ofMillis(100))
block.enriched
}
def "enriched block arrived in sources before reference block"() {
setup:
def enrichedBlock = TestingCommons.enrichedBlockForEthereum(100)
def block = TestingCommons.blockForEthereum(100)
Sinks.Many<BlockContainer> refSink = Sinks.many().multicast().directBestEffort()
def headRef = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> refSink.asFlux()
}
def headSource = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.just(enrichedBlock)
}
when:
def merge = new EnrichedMergedHead([headSource], headRef, Schedulers.boundedElastic(), new BlockReader(new ApiReaderMock()))
then:
StepVerifier.create(merge.getFlux())
.then { merge.start() }
.expectNoEvent(Duration.ofMillis(100))
.then { refSink.tryEmitNext(block) }
.expectNext(enrichedBlock)
.thenCancel()
.verify(Duration.ofMillis(200))
}
def "enriched block arrived in source after reference block, but before deadline"() {
setup:
def enrichedBlock = TestingCommons.enrichedBlockForEthereum(100)
def block = TestingCommons.blockForEthereum(100)
Sinks.Many<BlockContainer> sourceSink = Sinks.many().multicast().directBestEffort()
def headRef = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.just(block)
}
def headSource = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> sourceSink.asFlux()
}
when:
def merge = new EnrichedMergedHead([headSource], headRef, Schedulers.boundedElastic(), new BlockReader(new ApiReaderMock()))
then:
StepVerifier.create(merge.getFlux())
.then { merge.start() }
.expectNoEvent(Duration.ofMillis(600))
.then { sourceSink.tryEmitNext(enrichedBlock) }
.expectNext(enrichedBlock)
.thenCancel()
.verify(Duration.ofSeconds(1))
}
def "enriched blocks does not arrive before deadline"() {
setup:
def enrichedBlock = TestingCommons.enrichedBlockForEthereum(100)
def block = TestingCommons.blockForEthereum(100)
def headRef = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.just(block)
}
def headSource = Stub(TestHead) {
_ * isRunning() >> true
_ * getFlux() >> Flux.just(block)
}
def api = new ApiReaderMock().tap {
answer("eth_getBlockByHash", [block.hash.toHexWithPrefix(), false], enrichedBlock.toBlock())
}
when:
def merge = new EnrichedMergedHead([headSource], headRef, Schedulers.boundedElastic(), new BlockReader(api))
then:
StepVerifier.create(merge.getFlux())
.then { merge.start() }
.expectNoEvent(Duration.ofSeconds(1))
.expectNext(enrichedBlock)
.thenCancel()
.verify(Duration.ofMillis(1200))
}
class BlockReader implements Reader<BlockHash, BlockContainer> {
private ApiReaderMock mockApi
BlockReader(ApiReaderMock api) {
mockApi = api
}
Mono<BlockContainer> read(BlockHash hash) {
return mockApi.read(new JsonRpcRequest("eth_getBlockByHash", [hash.toHex(), false]))
.map {
def t = it
def a = 1
return it
}
.flatMap(JsonRpcResponse::requireResult)
.map { BlockContainer.fromEthereumJson(it, "test") }
}
}
class TestHead extends AbstractHead implements Lifecycle {
TestHead() {
super(new MostWorkForkChoice(), Schedulers.boundedElastic(), new BlockValidator.AlwaysValid(), 100_000)
}
@Override
void start() {
}
@Override
void stop() {
}
@Override
boolean isRunning() {
return false
}
}
}

View File

@@ -4,6 +4,7 @@ import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CurrentBlockCache
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.data.DefaultContainer
import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.reader.RpcReaderFactory
@@ -17,6 +18,7 @@ import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.etherjar.domain.TransactionId
import io.emeraldpay.etherjar.domain.Wei
import io.emeraldpay.etherjar.rpc.json.TransactionJson
import io.emeraldpay.etherjar.rpc.json.TransactionLogJson
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
import org.apache.commons.collections4.Factory
import reactor.core.publisher.Mono
@@ -128,6 +130,38 @@ class EthereumDirectReaderSpec extends Specification {
.verify(Duration.ofSeconds(1))
}
def "Reads logs by block hash"() {
setup:
def json = new TransactionLogJson().tap {
address = Address.from(address1)
blockHash = BlockHash.from(hash1)
}
def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false)
}
EthereumDirectReader reader = new EthereumDirectReader(
Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
)
reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getLogs", [Map.of("blockHash", hash1)])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes([json]), null, 1, resolver
)
)
}
}
when:
def act = reader.logsByHashReader.read(BlockId.from(hash1))
then:
StepVerifier.create(act)
.expectNextMatches { logs ->
logs.data[0].blockHash == BlockHash.from(hash1)
}
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Reads tx"() {
setup:
def json = new TransactionJson().tap {

View File

@@ -45,27 +45,17 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
when:
def act = connectBlockUpdates.extractUpdates(block)
.collectList().block(Duration.ofSeconds(3))
then:
act.size() == 2
act.size() == 1
with(act[0]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
it.type == ConnectBlockUpdates.UpdateType.NEW
}
with(act[1]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2")
it.type == ConnectBlockUpdates.UpdateType.NEW
}
}
@@ -79,27 +69,17 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
when:
def act = connectBlockUpdates.whenReplaced(block, "ConnectBlockUpdatesSpec")
.collectList().block(Duration.ofSeconds(3))
then:
act.size() == 2
act.size() == 1
with(act[0]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
it.type == ConnectBlockUpdates.UpdateType.DROP
}
with(act[1]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2")
it.type == ConnectBlockUpdates.UpdateType.DROP
}
}
@@ -113,10 +93,7 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
def block2 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
hash = BlockHash.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
@@ -124,10 +101,7 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
def block3 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
hash = BlockHash.from("0xdb1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7")
@@ -135,10 +109,7 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
parentHash = parent
timestamp = Instant.now()
transactions = [
new TransactionRefJson(TransactionId.from("0x9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af6c88df9d65ccc")),
new TransactionRefJson(TransactionId.from("0xdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe25c241a64e7ce536f"))
]
transactions = []
})
when:
@@ -177,10 +148,7 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
def block2 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
hash = BlockHash.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
@@ -188,10 +156,7 @@ class ConnectBlockUpdatesSpec extends Specification {
totalDifficulty = BigInteger.ONE
timestamp = Instant.now()
parentHash = parent
transactions = [
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
]
transactions = []
})
when:
@@ -200,29 +165,15 @@ class ConnectBlockUpdatesSpec extends Specification {
.collectList().block(Duration.ofSeconds(1))
then:
act.size() == 4
act.size() == 2
with(act[0]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
it.type == ConnectBlockUpdates.UpdateType.DROP
}
with(act[1]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
it.transactionId == TxId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2")
it.type == ConnectBlockUpdates.UpdateType.DROP
}
with(act[2]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
it.transactionId == TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
it.type == ConnectBlockUpdates.UpdateType.NEW
}
with(act[3]) {
it.blockNumber == 13412871
it.blockHash == BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
it.transactionId == TxId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2")
it.type == ConnectBlockUpdates.UpdateType.NEW
}
}
@@ -233,7 +184,7 @@ class ConnectBlockUpdatesSpec extends Specification {
1 * getFlux() >> Flux.never()
}
def up = Mock(GenericMultistream) {
1 * getEnrichedHead(Selector.empty) >> head
1 * getHead(Selector.empty) >> head
}
def connectBlockUpdates = new ConnectBlockUpdates(up, Schedulers.boundedElastic())

View File

@@ -20,6 +20,11 @@ import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.data.TxId
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.ethereum.EthereumDirectReader
import io.emeraldpay.etherjar.domain.Address
import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.etherjar.domain.TransactionId
import io.emeraldpay.etherjar.hex.HexData
import io.emeraldpay.etherjar.rpc.json.TransactionLogJson
import reactor.core.publisher.Mono
import spock.lang.Specification
@@ -27,28 +32,28 @@ import java.time.Duration
class ProduceLogsSpec extends Specification {
def logs = [new TransactionLogJson().tap {
blockHash = BlockHash.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
address = Address.from("0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d")
blockNumber = 1
logIndex = 1
data = HexData.empty()
transactionIndex = 1
topics = []
transactionHash = TransactionId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")
}]
def "Produce added as nothing with no logs"() {
setup:
String receipt = '{\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "blockNumber": "0x1",\n' +
' "from": "0x5e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "logs": [\n' +
' ],\n' +
' "transactionHash": "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af",\n' +
' "transactionIndex": "0x0"\n' +
' }'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
def logs = Mock(Reader) {
1 * it.read(BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")) >>
Mono.just(new EthereumDirectReader.Result<>([], null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def producer = new ProduceLogs(logs, Chain.ETHEREUM__MAINNET)
def update = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
1,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"),
"upstream"
)
when:
@@ -59,67 +64,18 @@ class ProduceLogsSpec extends Specification {
act.size() == 0
}
def "Produce added as nothing with null receipt"() {
def "Produce added with logs"() {
setup:
String receipt = 'null'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
def logs = Mock(Reader) {
1 * it.read(BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")) >>
Mono.just(new EthereumDirectReader.Result<>(logs, null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def producer = new ProduceLogs(logs, Chain.ETHEREUM__MAINNET)
def update = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
1,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"),
"upstream"
)
when:
def act = producer.produceAdded(update)
.collectList().block(Duration.ofSeconds(1))
then:
act.size() == 0
}
def "Produce added with single log"() {
setup:
String receipt = '{\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "blockNumber": "0x1",\n' +
' "from": "0x5e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "logs": [\n' +
' {\n' +
' "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "0x00000000000000000000000099897cb0e667d354b920fc38a40a5100b2a01566"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000000000000007505d91f0",\n' +
' "blockNumber": "0xc7f3b4",\n' +
' "transactionHash": "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af",\n' +
' "transactionIndex": "0x0",\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "logIndex": "0x0",\n' +
' "removed": false\n' +
' }' +
' ],\n' +
' "transactionHash": "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af",\n' +
' "transactionIndex": "0x0"\n' +
' }'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def update = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"),
"upstream"
)
when:
@@ -128,244 +84,27 @@ class ProduceLogsSpec extends Specification {
then:
act.size() == 1
with(act[0]) {
it.transactionHash.toHex() == "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"
}
}
def "Produce added with no data"() {
setup:
String receipt = '{\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "blockNumber": "0x1",\n' +
' "from": "0x5e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "logs": [\n' +
' {\n' +
' "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"\n' +
' ],\n' +
' "blockNumber": "0xc7f3b4",\n' +
' "transactionHash": "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af",\n' +
' "transactionIndex": "0x0",\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "logIndex": "0x0",\n' +
' "removed": false\n' +
' }' +
' ],\n' +
' "transactionHash": "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af",\n' +
' "transactionIndex": "0x0"\n' +
' }'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def update = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"),
"upstream"
)
when:
def act = producer.produceAdded(update)
.collectList().block(Duration.ofSeconds(1))
then:
act.size() == 1
with(act[0]) {
it.transactionHash.toHex() == "0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af"
// Geth actually renders it as null, so this check may be wrong
it.data != null && it.data.size == 0
}
}
def "Produce added with multiple logs"() {
setup:
String receipt = '{\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "blockNumber": "0x1",\n' +
' "from": "0x5e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "logs": [\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x00000000000000000000000075b8c48bdb04d426aed57b36bb835ad2dc321c30"\n' +
' ],\n' +
' "data": "0x0000000000000000000000000000000000000000000000013e7ec767db370000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb4",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000000000000000000000000000000000000000000000"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000000023636b7d513f0000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb5",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000001b46b72c5280f30fbe8a958b4f3c348fd0fd2e55"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000011316d590258fba0000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb6",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000001b46b72c5280f30fbe8a958b4f3c348fd0fd2e55"\n' +
' ],\n' +
' "data": "0x0000000000000000000000000000000000000000000014188a101e403a500000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb7",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x1b46b72c5280f30fbe8a958b4f3c348fd0fd2e55",\n' +
' "topics": [\n' +
' "0x90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a15",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000000000000000000000000000000000000000000000"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000011478b7c30abc300000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb8",\n' +
' "removed": false\n' +
' }' +
' ],\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57"\n' +
' }'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def update = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
"upstream"
)
when:
def act = producer.produceAdded(update)
.collectList().block(Duration.ofSeconds(1))
then:
act.size() == 5
act*.transactionHash.every { it.toHex() == "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec" }
act*.logIndex == [180, 181, 182, 183, 184]
act*.address.every { it.toHex() == "0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d" }
act*.removed.every { !it }
}
def "Produce removed"() {
setup:
String receipt = '{\n' +
' "blockHash": "0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da",\n' +
' "blockNumber": "0x1",\n' +
' "from": "0x5e78dd1e81ecdf078e029117eca98eaa71f46bdb",\n' +
' "logs": [\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x00000000000000000000000075b8c48bdb04d426aed57b36bb835ad2dc321c30"\n' +
' ],\n' +
' "data": "0x0000000000000000000000000000000000000000000000013e7ec767db370000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb4",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000000000000000000000000000000000000000000000"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000000023636b7d513f0000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb5",\n' +
' "removed": false\n' +
' },\n' +
' {\n' +
' "address": "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",\n' +
' "topics": [\n' +
' "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",\n' +
' "0x0000000000000000000000005c6006105b1b777a13d58d96393ad9e556882025",\n' +
' "0x0000000000000000000000001b46b72c5280f30fbe8a958b4f3c348fd0fd2e55"\n' +
' ],\n' +
' "data": "0x00000000000000000000000000000000000000000000011316d590258fba0000",\n' +
' "blockNumber": "0xccc493",\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57",\n' +
' "blockHash": "0x7e2661ac0e2f34dd2d6f449eea45aeec8470a0948af9daa33e684226640d819c",\n' +
' "logIndex": "0xb6",\n' +
' "removed": false\n' +
' }\n' +
' ],\n' +
' "transactionHash": "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec",\n' +
' "transactionIndex": "0x57"\n' +
' }'
def receipts = Mock(Reader) {
1 * it.read(TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")) >>
Mono.just(new EthereumDirectReader.Result<>(receipt.getBytes(), null))
def logs = Mock(Reader) {
1 * it.read(BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")) >>
Mono.just(new EthereumDirectReader.Result<>(logs, null))
}
def producer = new ProduceLogs(receipts, Chain.ETHEREUM__MAINNET)
def producer = new ProduceLogs(logs, Chain.ETHEREUM__MAINNET)
def update1 = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
ConnectBlockUpdates.UpdateType.NEW,
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
"upstream"
)
def update2 = new ConnectBlockUpdates.Update(
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
13412871,
ConnectBlockUpdates.UpdateType.DROP,
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
"upstream"
)
when:
@@ -376,9 +115,9 @@ class ProduceLogsSpec extends Specification {
.collectList().block(Duration.ofSeconds(1))
then:
act.size() == 3
act.size() == 1
act*.transactionHash.every { it.toHex() == "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec" }
act*.logIndex == [180, 181, 182]
act*.logIndex == [1]
act*.removed.every { it }
}
}