From f89c4cc7f2bff3ff74219050915e5a2b27380b98 Mon Sep 17 00:00:00 2001 From: Igor Artamonov Date: Mon, 30 Nov 2020 19:58:31 -0500 Subject: [PATCH] solution: refer to current height when requesting balance fix: #31 --- .../dshackle/rpc/TrackERC20Address.kt | 12 ++++-- .../upstream/ethereum/EthereumDirectReader.kt | 3 +- .../upstream/ethereum/EthereumReader.kt | 1 + .../rpc/TrackEthereumAddressSpec.groovy | 2 +- .../ethereum/EthereumDirectReaderSpec.groovy | 38 ++++++++++++++++++- .../ethereum/EthereumReaderSpec.groovy | 4 +- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt index 64c9233e..97349828 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt @@ -5,6 +5,7 @@ import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.config.TokensConfig +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream @@ -14,6 +15,7 @@ import io.emeraldpay.grpc.Chain import io.infinitape.etherjar.domain.Address import io.infinitape.etherjar.erc20.ERC20Token import io.infinitape.etherjar.hex.Hex32 +import io.infinitape.etherjar.hex.HexQuantity import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -84,10 +86,11 @@ class TrackERC20Address( } fun getBalance(addr: TrackedAddress): Mono { - return getUpstream(addr.chain) + val upstream = getUpstream(addr.chain) + return upstream .getDirectApi(Selector.empty) .flatMap { api -> - api.read(prepareEthCall(addr.token, addr.address)) + api.read(prepareEthCall(addr.token, addr.address, upstream.getHead())) .flatMap(JsonRpcResponse::requireStringResult) .map { Hex32.from(it).asQuantity().value @@ -95,11 +98,12 @@ class TrackERC20Address( } } - fun prepareEthCall(token: ERC20Token, target: Address): JsonRpcRequest { + fun prepareEthCall(token: ERC20Token, target: Address, head: Head): JsonRpcRequest { val call = token .readBalanceOf(target) .toJson() - return JsonRpcRequest("eth_call", listOf(call, "latest")) + val height = head.getCurrentHeight()?.let { HexQuantity.from(it).toHex() } ?: "latest" + return JsonRpcRequest("eth_call", listOf(call, height)) } fun getUpstream(chain: Chain): EthereumMultistream { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt index 112cb02d..ae19a8ab 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt @@ -89,7 +89,8 @@ class EthereumDirectReader( } balanceReader = object : Reader { override fun read(key: Address): Mono { - val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), "latest")) + val height = up.getHead().getCurrentHeight()?.let { HexQuantity.from(it).toHex() } ?: "latest" + val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), height)) return readWithQuorum(request) .timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key"))) .map { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumReader.kt index bb927f87..052e3ac2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumReader.kt @@ -130,6 +130,7 @@ open class EthereumReader( } fun balance(): Reader { + //TODO include height as part of cache? return CompoundReader( balanceCache, directReader.balanceReader ) diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy index d7876ff3..c879d7fa 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy @@ -101,7 +101,7 @@ class TrackEthereumAddressSpec extends Specification { TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams) apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2") - apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0xff98") + apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "0x1"], "0xff98") when: def flux = trackAddress.subscribe(req) then: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy index fdfd1e82..a8a235f6 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy @@ -7,6 +7,7 @@ import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.quorum.QuorumRpcReader import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.ApiSource +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest @@ -142,10 +143,13 @@ class EthereumDirectReaderSpec extends Specification { .verify(Duration.ofSeconds(1)) } - def "Reads balance"() { + def "Reads balance - height is unknown"() { setup: def up = Mock(Multistream) { 1 * getApiSource(_) >> Stub(ApiSource) + 1 * getHead() >> Mock(Head) { + 1 * getCurrentHeight() >> null + } } def calls = Mock(Factory) { 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM) @@ -171,4 +175,36 @@ class EthereumDirectReaderSpec extends Specification { .verify(Duration.ofSeconds(1)) } + def "Reads balance - height is known"() { + setup: + def up = Mock(Multistream) { + 1 * getApiSource(_) >> Stub(ApiSource) + 1 * getHead() >> Mock(Head) { + 1 * getCurrentHeight() >> 11_061_691 + } + } + def calls = Mock(Factory) { + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM) + } + EthereumDirectReader reader = new EthereumDirectReader( + up, Caches.default(), new CurrentBlockCache(), calls + ) + reader.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _) >> Mock(Reader) { + 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just( + new QuorumRpcReader.Result( + Global.objectMapper.writeValueAsBytes("0x100"), 1 + ) + ) + } + } + when: + def act = reader.balanceReader.read(Address.from(address1)) + then: + StepVerifier.create(act) + .expectNext(Wei.from("0x100")) + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy index 6806dc39..4a04c897 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy @@ -197,8 +197,10 @@ class EthereumReaderSpec extends Specification { def "Caches balance until block mined"() { setup: def api = TestingCommons.api() + // no height api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0x10") - api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff") + // height 101 + 1 => 102 => 0x66 + api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "0x66"], "0xff") EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) def upstreams = TestingCommons.multistream(upstream) def reader = new EthereumReader(upstreams, Caches.default(), calls)