solution: subscribe to "logs" events
This commit is contained in:
@@ -32,7 +32,7 @@ class NativeSubscribeSpec extends Specification {
|
||||
def "Call with empty params when not provided"() {
|
||||
setup:
|
||||
def subscribe = Mock(EthereumSubscribe) {
|
||||
1 * it.subscribe("newHeads", []) >> Flux.just("{}")
|
||||
1 * it.subscribe("newHeads", null) >> Flux.just("{}")
|
||||
}
|
||||
def up = Mock(EthereumMultistream) {
|
||||
1 * it.getSubscribe() >> subscribe
|
||||
@@ -56,13 +56,12 @@ class NativeSubscribeSpec extends Specification {
|
||||
def "Call with params when provided"() {
|
||||
setup:
|
||||
def subscribe = Mock(EthereumSubscribe) {
|
||||
1 * it.subscribe("newHeads", { params ->
|
||||
1 * it.subscribe("logs", { params ->
|
||||
println("params: $params")
|
||||
def ok = params.size() == 1 &&
|
||||
params[0] instanceof Map &&
|
||||
params[0]["address"] == "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" &&
|
||||
params[0]["topics"] instanceof List &&
|
||||
params[0]["topics"][0] == "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
|
||||
def ok = params instanceof Map &&
|
||||
params["address"] == "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" &&
|
||||
params["topics"] instanceof List &&
|
||||
params["topics"][0] == "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
|
||||
println("ok: $ok")
|
||||
ok
|
||||
}) >> Flux.just("{}")
|
||||
@@ -74,7 +73,7 @@ class NativeSubscribeSpec extends Specification {
|
||||
def nativeSubscribe = new NativeSubscribe(new MultistreamHolderMock(Chain.ETHEREUM, up))
|
||||
def call = BlockchainOuterClass.NativeSubscribeRequest.newBuilder()
|
||||
.setChainValue(Chain.ETHEREUM.id)
|
||||
.setMethod("newHeads")
|
||||
.setMethod("logs")
|
||||
.setPayload(ByteString.copyFromUtf8(
|
||||
'{"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", ' +
|
||||
'"topics": ["0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"]}'
|
||||
|
||||
@@ -52,6 +52,7 @@ import reactor.util.annotation.Nullable
|
||||
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.function.BiFunction
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Predicate
|
||||
@@ -63,6 +64,7 @@ class EthereumApiMock implements Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
private final ObjectMapper objectMapper = Global.objectMapper
|
||||
|
||||
String id = "default"
|
||||
AtomicInteger calls = new AtomicInteger(0)
|
||||
|
||||
EthereumApiMock() {
|
||||
}
|
||||
@@ -83,6 +85,7 @@ class EthereumApiMock implements Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
def predefined = predefined.find { it.isSame(request.method, request.params) }
|
||||
byte[] result = null
|
||||
JsonRpcError error = null
|
||||
calls.incrementAndGet()
|
||||
if (predefined != null) {
|
||||
if (predefined.exception != null) {
|
||||
predefined.onCalled()
|
||||
|
||||
@@ -17,10 +17,12 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.ReceiptRedisCache
|
||||
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.Multistream
|
||||
@@ -232,4 +234,47 @@ class EthereumReaderSpec extends Specification {
|
||||
then:
|
||||
act == Wei.from("0xff")
|
||||
}
|
||||
|
||||
def "Read receipt from upstream if cache is empty"() {
|
||||
setup:
|
||||
def api = TestingCommons.api()
|
||||
api.answerOnce("eth_getTransactionReceipt", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"], [
|
||||
transactionHash: "0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"
|
||||
])
|
||||
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||
def upstreams = TestingCommons.multistream(upstream)
|
||||
def reader = new EthereumReader(upstreams, Caches.default(), calls)
|
||||
reader.start()
|
||||
|
||||
when:
|
||||
def act = reader.receipts().read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
new String(act) == '{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'
|
||||
}
|
||||
|
||||
def "Read receipt from cache if available"() {
|
||||
setup:
|
||||
def api = TestingCommons.api()
|
||||
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||
def upstreams = TestingCommons.multistream(upstream)
|
||||
def receiptCache = Mock(ReceiptRedisCache) {
|
||||
1 * it.read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")) >>
|
||||
Mono.just('{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'.bytes)
|
||||
}
|
||||
def cashes = Caches.newBuilder()
|
||||
.setReceipts(receiptCache)
|
||||
.build()
|
||||
def reader = new EthereumReader(upstreams, cashes, calls)
|
||||
reader.start()
|
||||
|
||||
when:
|
||||
def act = reader.receipts().read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
new String(act) == '{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'
|
||||
api.calls.get() == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import spock.lang.Specification
|
||||
|
||||
class EthereumSubscribeSpec extends Specification {
|
||||
|
||||
def "read empty logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([:])
|
||||
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == []
|
||||
}
|
||||
|
||||
def "read single address logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
address: "0x829bd824b016326a401d083b33d092293333a830"
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == [
|
||||
Address.from("0x829bd824b016326a401d083b33d092293333a830")
|
||||
]
|
||||
act.topics == []
|
||||
|
||||
when:
|
||||
act = ethereumSubscribe.readLogsRequest([
|
||||
address: ["0x829bd824b016326a401d083b33d092293333a830"]
|
||||
])
|
||||
then:
|
||||
act.address == [
|
||||
Address.from("0x829bd824b016326a401d083b33d092293333a830")
|
||||
]
|
||||
act.topics == []
|
||||
}
|
||||
|
||||
def "ignores invalid address for logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
address: "829bd824b016326a401d083b33d092293333a830"
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == []
|
||||
}
|
||||
|
||||
def "read multi address logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
address: ["0x829bd824b016326a401d083b33d092293333a830", "0x401d083b33d092293333a83829bd824b016326a0"]
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == [
|
||||
Address.from("0x829bd824b016326a401d083b33d092293333a830"),
|
||||
Address.from("0x401d083b33d092293333a83829bd824b016326a0")
|
||||
]
|
||||
act.topics == []
|
||||
}
|
||||
|
||||
def "read single topic logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
topics: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == [
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
]
|
||||
|
||||
when:
|
||||
act = ethereumSubscribe.readLogsRequest([
|
||||
topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
|
||||
])
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == [
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
]
|
||||
}
|
||||
|
||||
def "read invalid topic for request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
topics: [
|
||||
"0x401d083b33d092293333a83829bd824b016326a0",
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
]
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == [
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
]
|
||||
}
|
||||
|
||||
def "read multi topic logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
topics: [
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
|
||||
]
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == []
|
||||
act.topics == [
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
Hex32.from("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
|
||||
]
|
||||
}
|
||||
|
||||
def "read full logs request"() {
|
||||
setup:
|
||||
def ethereumSubscribe = new EthereumSubscribe(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def act = ethereumSubscribe.readLogsRequest([
|
||||
address: "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",
|
||||
topics : [
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
|
||||
]
|
||||
])
|
||||
|
||||
then:
|
||||
act.address == [
|
||||
Address.from("0x298d492e8c1d909d3f63bc4a36c66c64acb3d695")
|
||||
]
|
||||
act.topics == [
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
Hex32.from("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.TransactionId
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import io.emeraldpay.etherjar.rpc.json.BlockJson
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Flux
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class ConnectBlockUpdatesSpec extends Specification {
|
||||
|
||||
def "Extracts updates"() {
|
||||
setup:
|
||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream))
|
||||
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
when:
|
||||
def act = connectBlockUpdates.extractUpdates(block)
|
||||
.collectList().block(Duration.ofSeconds(3))
|
||||
|
||||
then:
|
||||
act.size() == 2
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def "Produce DROP updates for replaced block"() {
|
||||
setup:
|
||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream))
|
||||
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
when:
|
||||
def act = connectBlockUpdates.whenReplaced(block)
|
||||
.collectList().block(Duration.ofSeconds(3))
|
||||
|
||||
then:
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def "Gets prev version if available"() {
|
||||
setup:
|
||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream))
|
||||
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
def block2 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
def block3 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0xdb1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7")
|
||||
number = 13412872
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af6c88df9d65ccc")),
|
||||
new TransactionRefJson(TransactionId.from("0xdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe25c241a64e7ce536f"))
|
||||
]
|
||||
})
|
||||
|
||||
when:
|
||||
def prev = connectBlockUpdates.findPrevious(block1)
|
||||
then:
|
||||
prev == null
|
||||
|
||||
when:
|
||||
prev = connectBlockUpdates.findPrevious(block2)
|
||||
then:
|
||||
prev == null
|
||||
|
||||
when:
|
||||
prev = connectBlockUpdates.findPrevious(block3)
|
||||
then:
|
||||
prev == null
|
||||
|
||||
when:
|
||||
connectBlockUpdates.remember(block1)
|
||||
prev = connectBlockUpdates.findPrevious(block2)
|
||||
then:
|
||||
prev == block1
|
||||
|
||||
when:
|
||||
prev = connectBlockUpdates.findPrevious(block3)
|
||||
then:
|
||||
prev == null
|
||||
}
|
||||
|
||||
def "Marks old txes as dropped before producing a new version of same block"() {
|
||||
setup:
|
||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream))
|
||||
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
def block2 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||
hash = BlockHash.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da")
|
||||
number = 13412871
|
||||
totalDifficulty = BigInteger.ONE
|
||||
timestamp = Instant.now()
|
||||
transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")),
|
||||
new TransactionRefJson(TransactionId.from("0x5c241a64e7ce536fdb6b8912091151f18a23dd71cc76a48a4b7d453e339efbe2"))
|
||||
]
|
||||
})
|
||||
|
||||
when:
|
||||
connectBlockUpdates.remember(block1)
|
||||
def act = connectBlockUpdates.extract(block2)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
|
||||
then:
|
||||
act.size() == 4
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def "Keeps connection"() {
|
||||
setup:
|
||||
def head = Mock(Head) {
|
||||
1 * getFlux() >> Flux.never()
|
||||
}
|
||||
def up = Mock(EthereumMultistream) {
|
||||
1 * getHead() >> head
|
||||
}
|
||||
def connectBlockUpdates = new ConnectBlockUpdates(up)
|
||||
|
||||
when:
|
||||
def a1 = connectBlockUpdates.connect()
|
||||
def a2 = connectBlockUpdates.connect()
|
||||
|
||||
then:
|
||||
a1 == a2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.TransactionId
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import reactor.core.publisher.Flux
|
||||
import spock.lang.Specification
|
||||
|
||||
class ConnectLogsSpec extends Specification {
|
||||
|
||||
def log1 = new LogMessage(
|
||||
Address.from("0x298d492e8c1d909d3f63bc4a36c66c64acb3d695"),
|
||||
BlockHash.empty(),
|
||||
100L,
|
||||
HexData.empty(),
|
||||
1L,
|
||||
[
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
],
|
||||
TransactionId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
|
||||
1L,
|
||||
false
|
||||
)
|
||||
|
||||
def log2 = new LogMessage(
|
||||
Address.from("0x63bc4a36c66c64acb3d695298d492e8c1d909d3f"),
|
||||
BlockHash.empty(),
|
||||
100L,
|
||||
HexData.empty(),
|
||||
1L,
|
||||
[
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
],
|
||||
TransactionId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
|
||||
1L,
|
||||
false
|
||||
)
|
||||
|
||||
def log3 = new LogMessage(
|
||||
Address.from("0x63bc4a36c66c64acb3d695298d492e8c1d909d3f"),
|
||||
BlockHash.empty(),
|
||||
100L,
|
||||
HexData.empty(),
|
||||
1L,
|
||||
[
|
||||
Hex32.from("0x952ba7f163c4a11628f55a4df523b3efddf252ad1be2c89b69c2b068fc378daa")
|
||||
],
|
||||
TransactionId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
|
||||
1L,
|
||||
false
|
||||
)
|
||||
|
||||
def log4 = new LogMessage(
|
||||
Address.from("0x4a36c66c64acb3d695298d492e8c1d909d3f63bc"),
|
||||
BlockHash.empty(),
|
||||
100L,
|
||||
HexData.empty(),
|
||||
1L,
|
||||
[
|
||||
Hex32.from("0x952ba7f163c4a11628f55a4df523b3efddf252ad1be2c89b69c2b068fc378daa")
|
||||
],
|
||||
TransactionId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec"),
|
||||
1L,
|
||||
false
|
||||
)
|
||||
|
||||
def "Filter is empty"() {
|
||||
setup:
|
||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def input = Flux.fromIterable([
|
||||
log1, log2, log3, log4
|
||||
])
|
||||
def act = input.transform(connectLogs.filtered([], []))
|
||||
.collectList().block()
|
||||
|
||||
then:
|
||||
act.size() == 4
|
||||
act[0] == log1
|
||||
act[1] == log2
|
||||
act[2] == log3
|
||||
act[3] == log4
|
||||
}
|
||||
|
||||
def "Filter by address"() {
|
||||
setup:
|
||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def input = Flux.fromIterable([
|
||||
log1, log2
|
||||
])
|
||||
def act = input.transform(connectLogs.filtered([Address.from("0x298d492e8c1d909d3f63bc4a36c66c64acb3d695")], []))
|
||||
.collectList().block()
|
||||
|
||||
then:
|
||||
act.size() == 1
|
||||
act[0] == log1
|
||||
}
|
||||
|
||||
def "Filter by topic"() {
|
||||
setup:
|
||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def input = Flux.fromIterable([
|
||||
log1, log2, log3, log4
|
||||
])
|
||||
def act = input.transform(connectLogs.filtered([], [Hex32.from("0x952ba7f163c4a11628f55a4df523b3efddf252ad1be2c89b69c2b068fc378daa")]))
|
||||
.collectList().block()
|
||||
|
||||
then:
|
||||
act.size() == 2
|
||||
act[0] == log3
|
||||
act[1] == log4
|
||||
}
|
||||
|
||||
|
||||
def "Filter by address and topic"() {
|
||||
setup:
|
||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumMultistream)
|
||||
when:
|
||||
def input = Flux.fromIterable([
|
||||
log1, log2, log3, log4
|
||||
])
|
||||
def act = input.transform(connectLogs.filtered([Address.from("0x63bc4a36c66c64acb3d695298d492e8c1d909d3f")], [Hex32.from("0x952ba7f163c4a11628f55a4df523b3efddf252ad1be2c89b69c2b068fc378daa")]))
|
||||
.collectList().block()
|
||||
|
||||
then:
|
||||
act.size() == 1
|
||||
act[0] == log3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import reactor.core.publisher.Mono
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class ProduceLogsSpec extends Specification {
|
||||
|
||||
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(receipt.getBytes())
|
||||
}
|
||||
def producer = new ProduceLogs(receipts)
|
||||
def update = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.NEW,
|
||||
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
|
||||
)
|
||||
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(receipt.getBytes())
|
||||
}
|
||||
def producer = new ProduceLogs(receipts)
|
||||
def update = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.NEW,
|
||||
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
|
||||
)
|
||||
when:
|
||||
def act = producer.produceAdded(update)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
|
||||
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(receipt.getBytes())
|
||||
}
|
||||
def producer = new ProduceLogs(receipts)
|
||||
def update = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.NEW,
|
||||
TxId.from("0x6c88df9d65ccc9351db65676c3581b29483e8dabb71c48ef7671c44b0d5568af")
|
||||
)
|
||||
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(receipt.getBytes())
|
||||
}
|
||||
def producer = new ProduceLogs(receipts)
|
||||
def update = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.NEW,
|
||||
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")
|
||||
)
|
||||
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*.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(receipt.getBytes())
|
||||
}
|
||||
def producer = new ProduceLogs(receipts)
|
||||
def update1 = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.NEW,
|
||||
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")
|
||||
)
|
||||
def update2 = new ConnectBlockUpdates.Update(
|
||||
BlockId.from("0x668b92d6b8c7db1350fd527fec4885ce5be2159b2b7daf6b126babdcbaa349da"),
|
||||
13412871,
|
||||
ConnectBlockUpdates.UpdateType.DROP,
|
||||
TxId.from("0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec")
|
||||
)
|
||||
when:
|
||||
// first need to produce them as added, because that's when it remembers logs to "remove"
|
||||
producer.produceAdded(update1)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
def act = producer.produceRemoved(update2)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
|
||||
then:
|
||||
act.size() == 3
|
||||
act*.transactionHash.every { it.toHex() == "0xb5e554178a94fd993111f2ae64cb708cb0899d7b5182024e70d5c468164a8bec" }
|
||||
act*.logIndex == [180, 181, 182]
|
||||
act*.removed.every { it }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.TransactionId
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import spock.lang.Specification
|
||||
|
||||
class LogMessageSpec extends Specification {
|
||||
|
||||
def "Serialize to a correct JSON"() {
|
||||
setup:
|
||||
def msg = new LogMessage(
|
||||
Address.from("0x011b6e24ffb0b5f5fcc564cf4183c5bbbc96d515"),
|
||||
BlockHash.from("0x48249c81bfced2e6fe2536126471b73d83c4f21de75f88a16feb57cc566b991b"),
|
||||
0xccf6e2,
|
||||
HexData.from("0x0000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f0000000000000000000000000000000000000000000000000000000000000009000000000000000000000000290328354c99b119d891a30d326bad92e36e78596782b7e23208a269f0b8262da05a2e22f4befd147ca89a47991d04b541087789"),
|
||||
0xe7,
|
||||
[
|
||||
Hex32.from("0x23be8e12e420b5da9fb98d8102572f640fb3c11a0085060472dfc0ed194b3cf7"),
|
||||
Hex32.from("0x000000000000000000000000000000000000000000000000000000000002bcff"),
|
||||
Hex32.from("0xd3847bbd7bdf7bf84c0a165d198f956f7ccffebdaf1413b5a4a77980d8b6a890")
|
||||
],
|
||||
TransactionId.from("0xc7529e79f78f58125abafeaea01fe3abdc6f45c173d5dfb36716cbc526e5b2d1"),
|
||||
0xa3,
|
||||
false)
|
||||
ObjectMapper objectMapper = Global.getObjectMapper()
|
||||
def exp = '{' +
|
||||
'"address":"0x011b6e24ffb0b5f5fcc564cf4183c5bbbc96d515",' +
|
||||
'"blockHash":"0x48249c81bfced2e6fe2536126471b73d83c4f21de75f88a16feb57cc566b991b",' +
|
||||
'"blockNumber":"0xccf6e2",' +
|
||||
'"data":"0x0000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f0000000000000000000000000000000000000000000000000000000000000009000000000000000000000000290328354c99b119d891a30d326bad92e36e78596782b7e23208a269f0b8262da05a2e22f4befd147ca89a47991d04b541087789",' +
|
||||
'"logIndex":"0xe7",' +
|
||||
'"topics":[' +
|
||||
'"0x23be8e12e420b5da9fb98d8102572f640fb3c11a0085060472dfc0ed194b3cf7",' +
|
||||
'"0x000000000000000000000000000000000000000000000000000000000002bcff",' +
|
||||
'"0xd3847bbd7bdf7bf84c0a165d198f956f7ccffebdaf1413b5a4a77980d8b6a890"' +
|
||||
'],' +
|
||||
'"transactionHash":"0xc7529e79f78f58125abafeaea01fe3abdc6f45c173d5dfb36716cbc526e5b2d1",' +
|
||||
'"transactionIndex":"0xa3",' +
|
||||
'"removed":false' +
|
||||
'}'
|
||||
when:
|
||||
def json = objectMapper.writeValueAsString(msg)
|
||||
|
||||
then:
|
||||
json == exp
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class NewHeadSpec extends Specification {
|
||||
class NewHeadMessageSpec extends Specification {
|
||||
|
||||
def "Serialize to a correct JSON"() {
|
||||
setup:
|
||||
NewHead obj = new NewHead(
|
||||
NewHeadMessage obj = new NewHeadMessage(
|
||||
0xc7f3b4,
|
||||
BlockHash.from("0xd3b7ae1a79f5418debae9b8e9318094298c087183be0f7a0151b0e76ba38d6bc"),
|
||||
BlockHash.from("0xcda7fd1d6ee2d5da7505a0634e27f41d5ae87a344cd75bb64c1dc0863fbe9c0a"),
|
||||
Reference in New Issue
Block a user