remove unused specific tracks (#326)
merge ethereum interfaces with generic ones
This commit is contained in:
@@ -43,7 +43,7 @@ class NativeSubscribeSpec extends Specification {
|
||||
1 * it.subscribe("newHeads", null, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
||||
}
|
||||
def up = Mock(EthereumPosMultiStream) {
|
||||
1 * it.tryProxy(_ as Selector.AnyLabelMatcher, call) >> null
|
||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
||||
1 * it.getEgressSubscription() >> subscribe
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class NativeSubscribeSpec extends Specification {
|
||||
}, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
||||
}
|
||||
def up = Mock(EthereumPosMultiStream) {
|
||||
1 * it.tryProxy(_ as Selector.AnyLabelMatcher, call) >> null
|
||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
||||
1 * it.getEgressSubscription() >> subscribe
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class NativeSubscribeSpec extends Specification {
|
||||
.setMethod("newHeads")
|
||||
.build()
|
||||
def up = Mock(EthereumPosMultiStream) {
|
||||
1 * it.tryProxy(_ as Selector.AnyLabelMatcher, call) >> Flux.just("{}")
|
||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> Flux.just("{}")
|
||||
0 * it.getEgressSubscription()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 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.rpc
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.XpubAddresses
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.data.SimpleUnspent
|
||||
import org.bitcoinj.core.Address
|
||||
import org.bitcoinj.params.MainNetParams
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.publisher.Sinks
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class TrackBitcoinAddressSpec extends Specification {
|
||||
|
||||
String hash1 = "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22"
|
||||
ObjectMapper objectMapper = Global.objectMapper
|
||||
|
||||
def "Correct sum for single"() {
|
||||
setup:
|
||||
def unspents = [
|
||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L)
|
||||
]
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def address = new TrackBitcoinAddress.Address(
|
||||
Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
when:
|
||||
def total = track.totalUnspent(address, false, unspents)
|
||||
|
||||
then:
|
||||
total.balance == 100
|
||||
total.utxo.isEmpty()
|
||||
}
|
||||
|
||||
def "Correct sum for few"() {
|
||||
setup:
|
||||
def unspents = [
|
||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L),
|
||||
new SimpleUnspent("17d1c4adf14b222e652c58d11435fa9ee2ddea000c6f5e20e6b715eb940fc28f", 0, 123L),
|
||||
]
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def address = new TrackBitcoinAddress.Address(
|
||||
Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
when:
|
||||
def total = track.totalUnspent(address, false, unspents)
|
||||
|
||||
then:
|
||||
total.balance == 223
|
||||
total.utxo.isEmpty()
|
||||
}
|
||||
|
||||
def "Correct sum for none"() {
|
||||
setup:
|
||||
def unspents = []
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def address = new TrackBitcoinAddress.Address(
|
||||
Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
when:
|
||||
def total = track.totalUnspent(address, false, unspents)
|
||||
|
||||
then:
|
||||
total.balance == 0
|
||||
total.utxo.isEmpty()
|
||||
}
|
||||
|
||||
def "Correct sum for none read"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def address = new TrackBitcoinAddress.Address(
|
||||
Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
def api = Mock(BitcoinMultistream) {
|
||||
1 * getReader() >> Mock(BitcoinReader) {
|
||||
1 * listUnspent(_) >> Mono.empty()
|
||||
}
|
||||
}
|
||||
when:
|
||||
def total = track.balanceForAddress(api, address, false).block()
|
||||
|
||||
then:
|
||||
total.balance == 0
|
||||
total.utxo.isEmpty()
|
||||
}
|
||||
|
||||
def "Correct sum for few with utxo"() {
|
||||
setup:
|
||||
def unspents = [
|
||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L),
|
||||
new SimpleUnspent("17d1c4adf14b222e652c58d11435fa9ee2ddea000c6f5e20e6b715eb940fc28f", 0, 123L),
|
||||
]
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def address = new TrackBitcoinAddress.Address(
|
||||
Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
when:
|
||||
def total = track.totalUnspent(address, true, unspents)
|
||||
|
||||
then:
|
||||
total.balance == 223
|
||||
total.utxo.size() == 2
|
||||
total.utxo[0].txid == "f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef"
|
||||
total.utxo[1].txid == "17d1c4adf14b222e652c58d11435fa9ee2ddea000c6f5e20e6b715eb940fc28f"
|
||||
}
|
||||
|
||||
def "One address for single provided"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressSingle(
|
||||
Common.SingleAddress.newBuilder()
|
||||
.setAddress("16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
when:
|
||||
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
|
||||
then:
|
||||
act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk"]
|
||||
}
|
||||
|
||||
def "Sorted addresses for multiple provided"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressMulti(
|
||||
Common.MultiAddress.newBuilder()
|
||||
.addAddresses(Common.SingleAddress.newBuilder().setAddress("16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk"))
|
||||
.addAddresses(Common.SingleAddress.newBuilder().setAddress("3BMqADKWoWHPASsUdHvnUL6E1jpZkMnLZz"))
|
||||
.addAddresses(Common.SingleAddress.newBuilder().setAddress("1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"))
|
||||
.addAddresses(Common.SingleAddress.newBuilder().setAddress("bc1qdthqvt6cllzej7uhdddrltdfsmnt7d0gl5ue5n"))
|
||||
)
|
||||
)
|
||||
.build()
|
||||
when:
|
||||
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
|
||||
then:
|
||||
act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk", "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", "3BMqADKWoWHPASsUdHvnUL6E1jpZkMnLZz", "bc1qdthqvt6cllzej7uhdddrltdfsmnt7d0gl5ue5n"]
|
||||
}
|
||||
|
||||
def "Empty for no address provided"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.build()
|
||||
when:
|
||||
def act = track.allAddresses(Stub(BitcoinMultistream), req).collectList().block()
|
||||
then:
|
||||
act == []
|
||||
}
|
||||
|
||||
def "Use active for xpub"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressXpub(
|
||||
// seed: chimney battle code relief era plug finish video patch dream pumpkin govern destroy fresh color
|
||||
Common.XpubAddress.newBuilder()
|
||||
.setXpub("zpub6tz4F49K5B4m7r7EyBKYM9K44eGECaQ2AfrCybq1w7ALFatz9856vrXxAPSrteDA4d5sjUPW3ACNq8wB2V3ugXVJxvAPAYPAYHsVm3VAncL")
|
||||
.setLimit(25)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
def xpubAddresses = Mock(XpubAddresses) {
|
||||
1 * activeAddresses(
|
||||
"zpub6tz4F49K5B4m7r7EyBKYM9K44eGECaQ2AfrCybq1w7ALFatz9856vrXxAPSrteDA4d5sjUPW3ACNq8wB2V3ugXVJxvAPAYPAYHsVm3VAncL",
|
||||
0,
|
||||
25
|
||||
) >> Flux.fromIterable([
|
||||
"bc1q25590fu8djhw9lvxxqz8ufjyfwup9h54u8fl6t",
|
||||
"bc1q3k6e6vawd5l5syu9nlxn2xsch9afgunl8dnz94",
|
||||
"bc1qu7hd6wycy686kakfps9c093szufjpwnh6rjs9s"
|
||||
]).map { Address.fromString(MainNetParams.get(), it) }
|
||||
}
|
||||
def multistream = Mock(BitcoinMultistream) {
|
||||
1 * getXpubAddresses() >> xpubAddresses
|
||||
}
|
||||
when:
|
||||
def act = track.allAddresses(multistream, req).collectList().block()
|
||||
then:
|
||||
act == [
|
||||
"bc1q25590fu8djhw9lvxxqz8ufjyfwup9h54u8fl6t",
|
||||
"bc1q3k6e6vawd5l5syu9nlxn2xsch9afgunl8dnz94",
|
||||
"bc1qu7hd6wycy686kakfps9c093szufjpwnh6rjs9s"
|
||||
]
|
||||
}
|
||||
|
||||
def "Build proto for common balance"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.valueOf(123456))
|
||||
when:
|
||||
def act = track.buildResponse(balance)
|
||||
then:
|
||||
act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
act.balance == "123456"
|
||||
act.asset.chain.number == Chain.BITCOIN__MAINNET.id
|
||||
act.asset.code == "BTC"
|
||||
}
|
||||
|
||||
def "Build proto for zero balance"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.ZERO)
|
||||
when:
|
||||
def act = track.buildResponse(balance)
|
||||
then:
|
||||
act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
act.balance == "0"
|
||||
act.asset.chain.number == Chain.BITCOIN__MAINNET.id
|
||||
act.asset.code == "BTC"
|
||||
}
|
||||
|
||||
def "Build proto for all bitcoins"() {
|
||||
setup:
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||
def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN__MAINNET, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.valueOf(21_000_000).multiply(BigInteger.TEN.pow(8)))
|
||||
when:
|
||||
def act = track.buildResponse(balance)
|
||||
then:
|
||||
act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
act.balance == "2100000000000000"
|
||||
act.asset.chain.number == Chain.BITCOIN__MAINNET.id
|
||||
act.asset.code == "BTC"
|
||||
}
|
||||
|
||||
def "Get update for a balance"() {
|
||||
setup:
|
||||
|
||||
def blocks = Sinks.many().unicast().onBackpressureBuffer()
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> Flux.concat(
|
||||
Flux.just(
|
||||
new BlockContainer(0L, BlockId.from(hash1), BigInteger.ZERO, Instant.now(), false, null, null, BlockId.from(hash1), [], 0, "TrackBitcoinAddressSpec")
|
||||
),
|
||||
blocks.asFlux()
|
||||
)
|
||||
}
|
||||
def upstream = null
|
||||
upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
2 * listUnspent(_) >>> [
|
||||
Mono.just([]),
|
||||
Mono.just([
|
||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 1230000L)
|
||||
])
|
||||
]
|
||||
}
|
||||
_ * getHead() >> head
|
||||
_ * cast(_) >> {
|
||||
upstream
|
||||
}
|
||||
}
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.BITCOIN__MAINNET, upstream)
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(upstreams)
|
||||
track.setBalanceAvailability(Chain.BITCOIN__MAINNET, true)
|
||||
|
||||
when:
|
||||
def resp = track.subscribe(BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAsset(Common.Asset.newBuilder().setChain(Common.ChainRef.CHAIN_BITCOIN__MAINNET))
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder().setAddressSingle(
|
||||
Common.SingleAddress.newBuilder().setAddress("1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
).map {
|
||||
it.balance
|
||||
}
|
||||
|
||||
then:
|
||||
StepVerifier.create(resp)
|
||||
.expectNext("0")
|
||||
.then {
|
||||
blocks.tryEmitNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, null, BlockId.from(hash1), [], 0, "TrackBitcoinAddressSpec"))
|
||||
}
|
||||
.expectNext("1230000")
|
||||
.then {
|
||||
blocks.tryEmitComplete()
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 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.rpc
|
||||
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.CachingMempoolData
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class TrackBitcoinTxSpec extends Specification {
|
||||
|
||||
def "loadMempool() returns not found when not found"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
|
||||
CachingMempoolData mempoolAccess = Mock(CachingMempoolData) {
|
||||
1 * get() >> Mono.just([
|
||||
"69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9",
|
||||
"d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350"
|
||||
])
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadMempool(upstream, "65ce58db064bd105b14dc76a0bce0df14653cf5263d22d17e78864cf272ee367")
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
it.found == false && it.mined == false && it.blockHash == null
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "loadMempool() returns ok when found"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
CachingMempoolData mempoolAccess = Mock(CachingMempoolData) {
|
||||
1 * get() >> Mono.just([
|
||||
"69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9",
|
||||
"d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350"
|
||||
])
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadMempool(upstream, "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9")
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
it.found == true && it.mined == false && it.blockHash == null
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "loadExiting() returns not found if not mined"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadExisting(upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
it.found == true && it.mined == false && it.blockHash == null
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "loadExiting() returns block if mined"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
height : 100
|
||||
])
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadExisting(upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
it.found == true && it.mined == true &&
|
||||
it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" &&
|
||||
it.height == 100
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Goes with confirmations"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
// start with the current block
|
||||
def next = Flux.fromIterable([10, 12, 13, 14, 15]).map { h ->
|
||||
def hash = BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f")
|
||||
new BlockContainer(h.longValue(), hash, BigInteger.ONE, Instant.now(), false, null, null, hash, [], 0, "unknown")
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> next
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
1 * getHead() >> head
|
||||
}
|
||||
def status = new TrackBitcoinTx.TxStatus(
|
||||
txid, true, 10, true, "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f", Instant.now(), BigInteger.ONE, 0
|
||||
)
|
||||
when:
|
||||
def act = track.withConfirmations(upstream, status)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches { it.confirmations == 1 }
|
||||
.expectNextMatches { it.confirmations == 3 }
|
||||
.expectNextMatches { it.confirmations == 4 }
|
||||
.expectNextMatches { it.confirmations == 5 }
|
||||
.expectNextMatches { it.confirmations == 6 }
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Wait until mined"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
// start with the current block
|
||||
def next = Flux.fromIterable([10, 12, 13]).map { h ->
|
||||
def hash = BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f")
|
||||
new BlockContainer(h.longValue(), hash, BigInteger.ONE, Instant.now(), false, null, null, hash, [], 0, "unknown")
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> next
|
||||
}
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
3 * getTx(txid) >>> [
|
||||
Mono.just([
|
||||
txid: txid
|
||||
]),
|
||||
Mono.just([
|
||||
txid: txid
|
||||
]),
|
||||
Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
height : 100
|
||||
])
|
||||
]
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
1 * getHead() >> head
|
||||
_ * getReader() >> api
|
||||
}
|
||||
def status = new TrackBitcoinTx.TxStatus(
|
||||
txid, false, null, false, null, null, null, 0
|
||||
)
|
||||
when:
|
||||
def act = track.untilMined(upstream, status)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches { it.mined && it.height == 100 && it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" }
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Check mempool until found"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
|
||||
Head head = Mock(Head) {
|
||||
_ * getFlux() >> Flux.empty()
|
||||
}
|
||||
CachingMempoolData mempoolAccess = Mock(CachingMempoolData) {
|
||||
4 * get() >>> [
|
||||
Mono.just([]),
|
||||
Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9"]),
|
||||
Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9", txid]),
|
||||
Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9", txid]) //second call when started over
|
||||
]
|
||||
}
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getHead() >> head
|
||||
_ * getReader() >> api
|
||||
}
|
||||
|
||||
when:
|
||||
def steps = StepVerifier.withVirtualTime {
|
||||
track.untilFound(Chain.BITCOIN__MAINNET, upstream, txid).take(1)
|
||||
}
|
||||
|
||||
then:
|
||||
steps
|
||||
.expectSubscription()
|
||||
.expectNoEvent(Duration.ofSeconds(3))
|
||||
.expectNextMatches { it.found && !it.mined }
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Subscribe to an existing tx"() {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(MultistreamHolder))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
_ * getTx(txid) >> Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
height : 1
|
||||
])
|
||||
_ * getBlock("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f") >> Mono.just([
|
||||
height : 1,
|
||||
chainwork: "01",
|
||||
time : 10000
|
||||
])
|
||||
}
|
||||
def next = Flux.fromIterable([10, 11, 12]).map { h ->
|
||||
def hash = BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f")
|
||||
new BlockContainer(h.longValue(), hash, BigInteger.ONE, Instant.now(), false, null, null, hash, [], 0, "unknown")
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
_ * getFlux() >> next
|
||||
}
|
||||
BitcoinMultistream upstream = Mock(BitcoinMultistream) {
|
||||
_ * getReader() >> api
|
||||
_ * getHead() >> head
|
||||
}
|
||||
|
||||
when:
|
||||
def act = track.subscribe(Chain.BITCOIN__MAINNET, upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches { it.found && it.mined && it.confirmations == 10 && it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" }
|
||||
.expectNextMatches { it.found && it.mined && it.confirmations == 11 }
|
||||
.expectNextMatches { it.found && it.mined && it.confirmations == 12 }
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package io.emeraldpay.dshackle.rpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.config.TokensConfig
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.SubscriptionConnect
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.ERC20Balance
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumEgressSubscription
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.ConnectLogs
|
||||
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.erc20.ERC20Token
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class TrackERC20AddressSpec extends Specification {
|
||||
|
||||
def "Init with single token"() {
|
||||
setup:
|
||||
MultistreamHolder ups = Mock(MultistreamHolder) {
|
||||
_ * isAvailable(Chain.ETHEREUM__MAINNET) >> true
|
||||
}
|
||||
TokensConfig tokens = new TokensConfig([
|
||||
new TokensConfig.Token().tap {
|
||||
id = "dai"
|
||||
blockchain = Chain.ETHEREUM__MAINNET
|
||||
name = "DAI"
|
||||
type = TokensConfig.Type.ERC20
|
||||
address = Address.from("0x6B175474E89094C44Da98b954EedeAC495271d0F")
|
||||
}
|
||||
])
|
||||
TrackERC20Address track = new TrackERC20Address(ups, tokens)
|
||||
when:
|
||||
track.init()
|
||||
def act = track.tokens
|
||||
def supportDai = track.isSupported(Chain.ETHEREUM__MAINNET, "DAI")
|
||||
def supportSai = track.isSupported(Chain.ETHEREUM__MAINNET, "SAI")
|
||||
|
||||
then:
|
||||
act.size() == 1
|
||||
with(act.keySet().first()) {
|
||||
chain == Chain.ETHEREUM__MAINNET
|
||||
name == "dai"
|
||||
}
|
||||
with(act.values().first()) {
|
||||
chain == Chain.ETHEREUM__MAINNET
|
||||
name == "dai"
|
||||
token != null
|
||||
}
|
||||
supportDai
|
||||
!supportSai
|
||||
}
|
||||
|
||||
def "Init without tokens"() {
|
||||
setup:
|
||||
MultistreamHolder ups = Mock(MultistreamHolder) {
|
||||
_ * isAvailable(Chain.ETHEREUM__MAINNET) >> true
|
||||
}
|
||||
|
||||
TokensConfig tokens = new TokensConfig([])
|
||||
TrackERC20Address track = new TrackERC20Address(ups, tokens)
|
||||
when:
|
||||
track.init()
|
||||
def act = track.tokens
|
||||
def supportDai = track.isSupported(Chain.ETHEREUM__MAINNET, "dai")
|
||||
def supportSai = track.isSupported(Chain.ETHEREUM__MAINNET, "sai")
|
||||
|
||||
then:
|
||||
act.size() == 0
|
||||
!supportDai
|
||||
!supportSai
|
||||
}
|
||||
|
||||
def "Init with two tokens"() {
|
||||
setup:
|
||||
MultistreamHolder ups = Mock(MultistreamHolder) {
|
||||
_ * isAvailable(Chain.ETHEREUM__MAINNET) >> true
|
||||
}
|
||||
|
||||
TokensConfig tokens = new TokensConfig([
|
||||
new TokensConfig.Token().tap {
|
||||
id = "dai"
|
||||
blockchain = Chain.ETHEREUM__MAINNET
|
||||
name = "DAI"
|
||||
type = TokensConfig.Type.ERC20
|
||||
address = Address.from("0x6B175474E89094C44Da98b954EedeAC495271d0F")
|
||||
},
|
||||
new TokensConfig.Token().tap {
|
||||
id = "sai"
|
||||
blockchain = Chain.ETHEREUM__MAINNET
|
||||
name = "SAI"
|
||||
type = TokensConfig.Type.ERC20
|
||||
address = Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")
|
||||
}
|
||||
])
|
||||
TrackERC20Address track = new TrackERC20Address(ups, tokens)
|
||||
when:
|
||||
track.init()
|
||||
def act = track.tokens
|
||||
def supportDai = track.isSupported(Chain.ETHEREUM__MAINNET, "dai")
|
||||
def supportSai = track.isSupported(Chain.ETHEREUM__MAINNET, "sai")
|
||||
|
||||
then:
|
||||
act.size() == 2
|
||||
with(act[act.keySet().find { it.name == "dai" }]) {
|
||||
chain == Chain.ETHEREUM__MAINNET
|
||||
name == "dai"
|
||||
}
|
||||
with(act[act.keySet().find { it.name == "sai" }]) {
|
||||
chain == Chain.ETHEREUM__MAINNET
|
||||
name == "sai"
|
||||
}
|
||||
supportDai
|
||||
supportSai
|
||||
}
|
||||
|
||||
def "Builds response"() {
|
||||
setup:
|
||||
TrackERC20Address track = new TrackERC20Address(Stub(MultistreamHolder), new TokensConfig([]))
|
||||
TrackERC20Address.TrackedAddress address = new TrackERC20Address.TrackedAddress(
|
||||
Chain.ETHEREUM__MAINNET,
|
||||
Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"),
|
||||
new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")),
|
||||
"test",
|
||||
BigInteger.valueOf(1234)
|
||||
)
|
||||
when:
|
||||
def act = track.buildResponse(address)
|
||||
then:
|
||||
act == BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(Common.SingleAddress.newBuilder().setAddress("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
||||
.setAsset(Common.Asset.newBuilder()
|
||||
.setChain(Common.ChainRef.CHAIN_ETHEREUM__MAINNET)
|
||||
.setCode("TEST")
|
||||
)
|
||||
.setBalance("1234")
|
||||
.build()
|
||||
}
|
||||
|
||||
def "Check balance when event happens"() {
|
||||
setup:
|
||||
def events = [
|
||||
new LogMessage(
|
||||
Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"),
|
||||
BlockHash.from("0x0c0d2969c843d0b61fbab1b2302cf24d6681b2ae0a140a3c2908990d048f7631"),
|
||||
13668750,
|
||||
HexData.from("0x0000000000000000000000000000000000000000000000000000000048f2fc7b"),
|
||||
1,
|
||||
[
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
Hex32.from("0x000000000000000000000000b02f1329d6a6acef07a763258f8509c2847a0a3e"),
|
||||
Hex32.from("0x00000000000000000000000016c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b")
|
||||
],
|
||||
TransactionId.from("0x5a7898e27120575c33d3d0179af3b6353c7268bbad4255df079ed26b743a21a5"),
|
||||
1,
|
||||
false,
|
||||
"unknown"
|
||||
)
|
||||
]
|
||||
|
||||
def connect = Mock(SubscriptionConnect) {
|
||||
1 * connect(Selector.empty) >> {
|
||||
Flux.fromIterable(events)
|
||||
}
|
||||
}
|
||||
def logs = Mock(ConnectLogs) {
|
||||
1 * create(
|
||||
[Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")],
|
||||
[Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")],
|
||||
) >> { args ->
|
||||
println("ConnectLogs.start $args")
|
||||
connect
|
||||
}
|
||||
}
|
||||
def sub = Mock(EthereumEgressSubscription) {
|
||||
1 * getLogs() >> logs
|
||||
}
|
||||
def up = Mock(EthereumPosMultiStream) {
|
||||
1 * getEgressSubscription() >> sub
|
||||
_ * cast(EthereumPosMultiStream) >> { args ->
|
||||
it
|
||||
}
|
||||
}
|
||||
def mup = Mock(MultistreamHolder) {
|
||||
_ * getUpstream(Chain.ETHEREUM__MAINNET) >> up
|
||||
}
|
||||
TokensConfig tokens = new TokensConfig([
|
||||
new TokensConfig.Token().tap {
|
||||
id = "test"
|
||||
blockchain = Chain.ETHEREUM__MAINNET
|
||||
name = "TEST"
|
||||
type = TokensConfig.Type.ERC20
|
||||
address = Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")
|
||||
}
|
||||
])
|
||||
TrackERC20Address track = new TrackERC20Address(mup, tokens)
|
||||
track.init()
|
||||
track.erc20Balance = Mock(ERC20Balance) {
|
||||
2 * it.getBalance(_, _, _) >>> [
|
||||
Mono.just(100000.toBigInteger()),
|
||||
Mono.just(150000.toBigInteger())
|
||||
]
|
||||
}
|
||||
def request = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressSingle(Common.SingleAddress.newBuilder().setAddress("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
||||
)
|
||||
.setAsset(
|
||||
Common.Asset.newBuilder()
|
||||
.setChain(Common.ChainRef.CHAIN_ETHEREUM__MAINNET)
|
||||
.setCode("TEST")
|
||||
)
|
||||
.build()
|
||||
when:
|
||||
def act = track.subscribe(request)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
println("Received: $it")
|
||||
it.getBalance() == "100000"
|
||||
}
|
||||
.expectNextMatches {
|
||||
println("Received: $it")
|
||||
it.getBalance() == "150000"
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 ETCDEV GmbH
|
||||
* Copyright (c) 2020 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.rpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class TrackEthereumAddressSpec extends Specification {
|
||||
|
||||
def chain = Common.ChainRef.CHAIN_ETHEREUM__MAINNET
|
||||
def address1 = "0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f"
|
||||
def address1Proto = Common.SingleAddress.newBuilder()
|
||||
.setAddress(address1)
|
||||
def etherAsset = Common.Asset.newBuilder()
|
||||
.setChain(chain)
|
||||
.setCode("ETHER")
|
||||
|
||||
|
||||
def "get balance"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAsset(etherAsset)
|
||||
.setAddress(Common.AnyAddress.newBuilder().setAddressSingle(address1Proto).build())
|
||||
.build()
|
||||
def exp = BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(address1Proto)
|
||||
.setAsset(etherAsset)
|
||||
.setBalance("1234567890")
|
||||
.build()
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams)
|
||||
|
||||
apiMock.answer("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2")
|
||||
when:
|
||||
def flux = trackAddress.getBalance(req)
|
||||
then:
|
||||
StepVerifier.create(flux)
|
||||
.expectNext(exp)
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "recheck address after each block"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAsset(etherAsset)
|
||||
.setAddress(Common.AnyAddress.newBuilder().setAddressSingle(address1Proto).build())
|
||||
.build()
|
||||
def exp1 = BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(address1Proto)
|
||||
.setAsset(etherAsset)
|
||||
.setBalance("1234567890")
|
||||
.build()
|
||||
def exp2 = BlockchainOuterClass.AddressBalance.newBuilder()
|
||||
.setAddress(address1Proto)
|
||||
.setAsset(etherAsset)
|
||||
.setBalance("65432")
|
||||
.build()
|
||||
|
||||
def hash = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")
|
||||
def block2 = new BlockJson().with {
|
||||
it.number = 1
|
||||
it.totalDifficulty = 100
|
||||
it.hash = hash
|
||||
it.parentHash = hash
|
||||
it.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
return it
|
||||
}
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams)
|
||||
|
||||
apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2")
|
||||
apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "0x1"], "0xff98")
|
||||
when:
|
||||
def flux = trackAddress.subscribe(req)
|
||||
then:
|
||||
StepVerifier.create(flux)
|
||||
.expectNext(exp1).as("First block")
|
||||
.then {
|
||||
upstreamMock.nextBlock(BlockContainer.from(block2))
|
||||
}
|
||||
.expectNext(exp2).as("Second block")
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(2))
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 ETCDEV GmbH
|
||||
* Copyright (c) 2020 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.rpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.DynamicMergedHead
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.TransactionId
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionJson
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import reactor.test.StepVerifier
|
||||
import reactor.test.scheduler.VirtualTimeScheduler
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
def chain = Common.ChainRef.CHAIN_ETHEREUM__MAINNET
|
||||
def txId = "0xba61ce4672751fd6086a9ac2b55547a5555af17535b6c0334ede2ecb6d64070a"
|
||||
def parent = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")
|
||||
|
||||
def "Gives details for an old transaction"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
.setChain(chain)
|
||||
.setConfirmationLimit(6)
|
||||
.setTxId(txId)
|
||||
.build()
|
||||
|
||||
def blockJson = new BlockJson().with {
|
||||
it.hash = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")
|
||||
it.timestamp = Instant.ofEpochMilli(156400000000)
|
||||
it.number = 100
|
||||
it.parentHash = parent
|
||||
it.totalDifficulty = BigInteger.valueOf(500)
|
||||
it
|
||||
}
|
||||
|
||||
def blockHeadJson = new BlockJson().with {
|
||||
it.hash = BlockHash.from("0x552d882f16f34a9dba2ccdc05c0a6a27c22a0e65cbc1b52a8ca60562112c6060")
|
||||
it.timestamp = Instant.ofEpochMilli(156400200000)
|
||||
it.number = 108
|
||||
it.totalDifficulty = BigInteger.valueOf(800)
|
||||
it.parentHash = parent
|
||||
it.transactions = []
|
||||
it
|
||||
}
|
||||
|
||||
|
||||
def txJson = new TransactionJson().with {
|
||||
it.hash = TransactionId.from("0xba61ce4672751fd6086a9ac2b55547a5555af17535b6c0334ede2ecb6d64070a")
|
||||
it.blockHash = blockJson.hash
|
||||
it.blockNumber = blockJson.number
|
||||
it.nonce = 1
|
||||
it
|
||||
}
|
||||
|
||||
blockJson.transactions = [new TransactionRefJson(txJson.hash)]
|
||||
|
||||
def exp1 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(true)
|
||||
.setMined(true)
|
||||
.setConfirmations(8 + 1)
|
||||
.setBlock(
|
||||
Common.BlockInfo.newBuilder()
|
||||
.setHeight(blockJson.number)
|
||||
.setBlockId(blockJson.hash.toHex().substring(2))
|
||||
.setTimestamp(blockJson.timestamp.toEpochMilli())
|
||||
).build()
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJson)
|
||||
apiMock.answer("eth_getBlockByHash", [blockJson.hash.toHex(), false], blockJson)
|
||||
upstreamMock.nextBlock(BlockContainer.from(blockHeadJson))
|
||||
|
||||
when:
|
||||
def flux = trackTx.subscribe(req)
|
||||
then:
|
||||
StepVerifier.create(flux)
|
||||
.expectNext(exp1)
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(4))
|
||||
}
|
||||
|
||||
def "Wait for unknown transaction"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
((EthereumPosMultiStream) upstreams.getUpstream(Chain.ETHEREUM__MAINNET)).head = Mock(DynamicMergedHead) {
|
||||
_ * getFlux() >> Flux.empty()
|
||||
}
|
||||
def scheduler = VirtualTimeScheduler.create(true)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, scheduler)
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null)
|
||||
|
||||
when:
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM__MAINNET, Instant.now(), TransactionId.from(txId), 6)
|
||||
def act = StepVerifier.withVirtualTime(
|
||||
{ trackTx.subscribe(tx, upstreams.getUpstream(Chain.ETHEREUM__MAINNET).cast(EthereumPosMultiStream)) },
|
||||
{ scheduler },
|
||||
5)
|
||||
|
||||
then:
|
||||
act
|
||||
.expectSubscription()
|
||||
.expectNoEvent(Duration.ofSeconds(20)).as("Waited for updates")
|
||||
.thenAwait(Duration.ofSeconds(2))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Known transaction when not mined"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
.setChain(chain)
|
||||
.setConfirmationLimit(6)
|
||||
.setTxId(txId)
|
||||
.build()
|
||||
def exp1 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(false)
|
||||
.setMined(false)
|
||||
.build()
|
||||
def exp2 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(true)
|
||||
.setMined(false)
|
||||
.build()
|
||||
def txJson = new TransactionJson().with {
|
||||
it.hash = TransactionId.from(txId)
|
||||
it.nonce = 1
|
||||
it
|
||||
}
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null, 2)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJson)
|
||||
|
||||
when:
|
||||
def act = trackTx.subscribe(req).take(2)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectSubscription()
|
||||
.expectNext(exp1).as("Unknown tx")
|
||||
.expectNext(exp2).as("Found in mempool")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(4))
|
||||
}
|
||||
|
||||
def "New block makes tx mined"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM__MAINNET, Instant.now(), TransactionId.from(txId), 6)
|
||||
def block = new BlockContainer(
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null,
|
||||
BlockId.from(txId), [TxId.from(txId)], 0, "unknown"
|
||||
)
|
||||
|
||||
when:
|
||||
def act = trackTx.onNewBlock(tx, block)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext(tx.withStatus(true, 100, true, BlockHash.from(txId), block.timestamp, BigInteger.ONE, 1))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "New block without current tx requires a call"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM__MAINNET, Instant.now(), TransactionId.from(txId), 6)
|
||||
def block = new BlockContainer(
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null, BlockId.from(txId),
|
||||
[TxId.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")],
|
||||
0, "unknown"
|
||||
)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null)
|
||||
|
||||
when:
|
||||
def act = trackTx.onNewBlock(tx, block)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@Ignore
|
||||
def "Starts to follow new transaction"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
.setChain(chain)
|
||||
.setConfirmationLimit(4)
|
||||
.setTxId(txId)
|
||||
.build()
|
||||
|
||||
List<BlockJson<TransactionRefJson>> blocks = (0..9).collect { i ->
|
||||
return new BlockJson().with {
|
||||
it.hash = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a2000${i}")
|
||||
it.timestamp = Instant.ofEpochMilli(156400000000 + i * 10000)
|
||||
it.setNumber(100L + i.longValue())
|
||||
it.parentHash = parent
|
||||
it.totalDifficulty = BigInteger.valueOf(500 + i)
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
def txJsonBroadcasted = new TransactionJson().with {
|
||||
it.hash = TransactionId.from("0xba61ce4672751fd6086a9ac2b55547a5555af17535b6c0334ede2ecb6d64070a")
|
||||
it.blockHash = null
|
||||
it.blockNumber = null
|
||||
it.nonce = 1
|
||||
it
|
||||
}
|
||||
|
||||
def txJsonMined = new TransactionJson().with {
|
||||
it.hash = TransactionId.from("0xba61ce4672751fd6086a9ac2b55547a5555af17535b6c0334ede2ecb6d64070a")
|
||||
it.blockHash = blocks[2].hash
|
||||
it.blockNumber = blocks[2].number
|
||||
it.nonce = 1
|
||||
it
|
||||
}
|
||||
|
||||
def exp1 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(false)
|
||||
.setMined(false)
|
||||
.setConfirmations(0)
|
||||
|
||||
def exp2 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(true)
|
||||
.setMined(true)
|
||||
.setBlock(
|
||||
Common.BlockInfo.newBuilder()
|
||||
.setHeight(blocks[2].number)
|
||||
.setBlockId(blocks[2].hash.toHex().substring(2))
|
||||
.setTimestamp(blocks[2].timestamp.toEpochMilli())
|
||||
)
|
||||
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
def multi = Mock(EthereumPosMultiStream) {
|
||||
_ * getHead() >> upstreamMock.getHead()
|
||||
}
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, multi)
|
||||
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], null)
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], txJsonBroadcasted)
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], txJsonBroadcasted)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJsonMined)
|
||||
blocks.forEach { block ->
|
||||
apiMock.answer("eth_getBlockByHash", [block.hash.toHex(), false], block)
|
||||
}
|
||||
|
||||
upstreamMock.blocks = Flux.fromIterable(blocks)
|
||||
.map { block ->
|
||||
BlockContainer.from(block)
|
||||
}
|
||||
|
||||
when:
|
||||
def flux = trackTx.subscribe(req)
|
||||
then:
|
||||
StepVerifier.create(flux)
|
||||
.expectNext(exp1.build()).as("Just empty")
|
||||
.expectNext(exp1.setBroadcasted(true).build()).as("Found in mempool")
|
||||
.expectNext(exp2.setConfirmations(1).build()).as("Mined")
|
||||
.expectNext(exp2.setConfirmations(2).build()).as("Confirmed 2")
|
||||
.expectNext(exp2.setConfirmations(3).build()).as("Confirmed 3")
|
||||
.expectNext(exp2.setConfirmations(4).build()).as("Confirmed 4")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(10))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,11 +109,11 @@ class MultistreamHolderMock implements MultistreamHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
EthereumCachingReader getReader() {
|
||||
EthereumCachingReader getCachingReader() {
|
||||
if (customReader != null) {
|
||||
return customReader
|
||||
}
|
||||
return super.getReader()
|
||||
return super.getCachingReader()
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -218,7 +218,7 @@ class MultistreamSpec extends Specification {
|
||||
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up1, up2], Caches.default())
|
||||
|
||||
when:
|
||||
def act = multiStream.tryProxy(new Selector.LabelMatcher("provider", ["internal"]), call)
|
||||
def act = multiStream.tryProxySubscribe(new Selector.LabelMatcher("provider", ["internal"]), call)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
@@ -243,7 +243,7 @@ class MultistreamSpec extends Specification {
|
||||
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up2], Caches.default())
|
||||
|
||||
when:
|
||||
def act = multiStream.tryProxy(new Selector.LabelMatcher("provider", ["internal"]), call)
|
||||
def act = multiStream.tryProxySubscribe(new Selector.LabelMatcher("provider", ["internal"]), call)
|
||||
|
||||
then:
|
||||
!act
|
||||
@@ -356,11 +356,6 @@ class MultistreamSpec extends Specification {
|
||||
return this
|
||||
}
|
||||
|
||||
@Override
|
||||
ChainFees getFeeEstimation() {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() {
|
||||
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
/**
|
||||
* 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.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ChainFees
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import reactor.core.publisher.Mono
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class BitcoinFeesSpec extends Specification {
|
||||
|
||||
def block3 = [
|
||||
hash: "3300000000033327aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
|
||||
tx : [
|
||||
"d5a67d8f99ad05ece282bc8714da55cbd2266d9d7bae98fd5dda3b55d1696fb8",
|
||||
"e17eca6b595da9c1c7b883fe505184d7ff586315d5b8a0c476198f0d49a75ad3",
|
||||
"1735dfef80bcfdc443943dcbc8d26a4133da87c5c81d9e2a1e0a0e5f59d1ef96",
|
||||
"c83c99e50946a6d30bc344a24401ad0ff0598425d73a5f74d6545e15ae588321",
|
||||
"0d4f85f840f09f317d0b202725acee868b2e969078bb6a6b89fc3e0bc1216c6a",
|
||||
"4d56b1a0992a3fa3132083ce29e203165ef4768f028d98a738da79dd26ff91e2",
|
||||
]
|
||||
]
|
||||
def block2 = [
|
||||
hash: "2200000000022227aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
|
||||
tx : [
|
||||
"87cc7d37851af41da60a5d14e40983d0a6750ffcdf1f2ae1291bfc1e712f0712",
|
||||
"3bde80635c62e81ba86d68a1c78ac47b1a02d8ef334fdf6a7b6e8acc0a475f20",
|
||||
"2f4f413c07ac072918e12fcac4afd8d30a76dfe49ac68d46d582c3a130772734",
|
||||
"f29ecfa5b692cfc46b1eae29416a35782657f0bde2ae44acde882e477e8624d8",
|
||||
]
|
||||
]
|
||||
|
||||
def tx4D5 = [
|
||||
hash : "4d56b1a0992a3fa3132083ce29e203165ef4768f028d98a738da79dd26ff91e2",
|
||||
size : 223,
|
||||
vsize: 142,
|
||||
vin : [
|
||||
[txid: "3bde80635c62e81ba86d68a1c78ac47b1a02d8ef334fdf6a7b6e8acc0a475f20", vout: 1]
|
||||
],
|
||||
vout : [
|
||||
["value": 0.00029163, "n": 0],
|
||||
["value": 0.00144221, "n": 1]
|
||||
]
|
||||
]
|
||||
def txF29 = [
|
||||
hash : "f29ecfa5b692cfc46b1eae29416a35782657f0bde2ae44acde882e477e8624d8",
|
||||
size : 2900,
|
||||
vsize: 1366,
|
||||
vin : [
|
||||
[txid: "37e19fad6c3e5fafc431ee731428452dc01f56b0bc4d9fe876dcee58384934ec", "vout": 0],
|
||||
[txid: "3da079b10451ba1cde42d9a0f485e79151e5d4216930561227be5471921af17f", "vout": 0]
|
||||
],
|
||||
vout : [
|
||||
["value": 1.20100000, "n": 0],
|
||||
["value": 0.00210984, "n": 1]
|
||||
]
|
||||
]
|
||||
|
||||
def tx3BD = [
|
||||
hash: "3bde80635c62e81ba86d68a1c78ac47b1a02d8ef334fdf6a7b6e8acc0a475f20",
|
||||
size: 292,
|
||||
vin : [
|
||||
],
|
||||
vout: [
|
||||
["value": 0.00400000, "n": 0],
|
||||
["value": 0.00200000, "n": 1],
|
||||
]
|
||||
]
|
||||
def tx37E = [
|
||||
hash : "37e19fad6c3e5fafc431ee731428452dc01f56b0bc4d9fe876dcee58384934ec",
|
||||
size : 292,
|
||||
vsize: 200,
|
||||
vin : [
|
||||
],
|
||||
vout : [
|
||||
["value": 1.00000000, "n": 0],
|
||||
]
|
||||
]
|
||||
def tx3DA = [
|
||||
hash : "3da079b10451ba1cde42d9a0f485e79151e5d4216930561227be5471921af17f",
|
||||
size : 297,
|
||||
vsize: 201,
|
||||
vin : [
|
||||
],
|
||||
vout : [
|
||||
["value": 0.21000000, "n": 0],
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def "fetch tx amount"() {
|
||||
setup:
|
||||
def reader = Mock(BitcoinReader) {
|
||||
1 * it.getTx("4d56b1a0992a3fa3132083ce29e203165ef4768f028d98a738da79dd26ff91e2") >> Mono.just(tx4D5)
|
||||
}
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), reader, 3)
|
||||
|
||||
when:
|
||||
def amount = fees.getTxOutAmount("4d56b1a0992a3fa3132083ce29e203165ef4768f028d98a738da79dd26ff91e2", 0)
|
||||
.block(Duration.ofSeconds(1))
|
||||
then:
|
||||
amount == 29163
|
||||
}
|
||||
|
||||
def "extract amounts"() {
|
||||
setup:
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), Stub(BitcoinReader), 3)
|
||||
|
||||
when:
|
||||
def act = fees.extractVOuts(txF29)
|
||||
then:
|
||||
act == [120100000L, 210984L]
|
||||
}
|
||||
|
||||
def "extract vsize when available"() {
|
||||
setup:
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), Stub(BitcoinReader), 3)
|
||||
|
||||
when:
|
||||
def act = fees.extractSize(tx4D5)
|
||||
then:
|
||||
act == 142
|
||||
}
|
||||
|
||||
def "extract size when vsize unavailable"() {
|
||||
setup:
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), Stub(BitcoinReader), 3)
|
||||
|
||||
when:
|
||||
def act = fees.extractSize(tx3BD)
|
||||
then:
|
||||
act == 292
|
||||
}
|
||||
|
||||
def "calculates fee"() {
|
||||
setup:
|
||||
def reader = Mock(BitcoinReader) {
|
||||
1 * it.getTx("3bde80635c62e81ba86d68a1c78ac47b1a02d8ef334fdf6a7b6e8acc0a475f20") >> Mono.just(tx3BD)
|
||||
}
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), reader, 3)
|
||||
when:
|
||||
def act = fees.calculateFee(tx4D5).block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == 200000 - (29163 + 144221)
|
||||
}
|
||||
|
||||
def "calculates fee with multiple inputs"() {
|
||||
setup:
|
||||
def reader = Mock(BitcoinReader) {
|
||||
1 * it.getTx("37e19fad6c3e5fafc431ee731428452dc01f56b0bc4d9fe876dcee58384934ec") >> Mono.just(tx37E)
|
||||
1 * it.getTx("3da079b10451ba1cde42d9a0f485e79151e5d4216930561227be5471921af17f") >> Mono.just(tx3DA)
|
||||
}
|
||||
def fees = new BitcoinFees(Stub(BitcoinMultistream), reader, 3)
|
||||
when:
|
||||
def act = fees.calculateFee(txF29).block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == (100000000 + 21000000) - (120100000 + 210984)
|
||||
}
|
||||
|
||||
def "get average bottom fee"() {
|
||||
setup:
|
||||
def ups = Mock(BitcoinMultistream) {
|
||||
_ * getHead() >> Mock(Head) {
|
||||
_ * getCurrentHeight() >> 100
|
||||
}
|
||||
}
|
||||
def reader = Mock(BitcoinReader) {
|
||||
1 * it.getBlock(100) >> Mono.just(block3)
|
||||
1 * it.getBlock(99) >> Mono.just(block2)
|
||||
// last txes on those blocks
|
||||
1 * it.getTx("4d56b1a0992a3fa3132083ce29e203165ef4768f028d98a738da79dd26ff91e2") >> Mono.just(tx4D5)
|
||||
1 * it.getTx("f29ecfa5b692cfc46b1eae29416a35782657f0bde2ae44acde882e477e8624d8") >> Mono.just(txF29)
|
||||
// their inputs
|
||||
1 * it.getTx("3bde80635c62e81ba86d68a1c78ac47b1a02d8ef334fdf6a7b6e8acc0a475f20") >> Mono.just(tx3BD)
|
||||
1 * it.getTx("37e19fad6c3e5fafc431ee731428452dc01f56b0bc4d9fe876dcee58384934ec") >> Mono.just(tx37E)
|
||||
1 * it.getTx("3da079b10451ba1cde42d9a0f485e79151e5d4216930561227be5471921af17f") >> Mono.just(tx3DA)
|
||||
}
|
||||
def fees = new BitcoinFees(ups, reader, 3)
|
||||
|
||||
when:
|
||||
def act = fees.estimate(ChainFees.Mode.AVG_LAST, 2).block(Duration.ofSeconds(1))
|
||||
|
||||
then:
|
||||
act.hasBitcoinStd()
|
||||
// first tx fee: 187.43661971830985915493
|
||||
// second tx fee: 504.40409956076134699854
|
||||
// average is 345
|
||||
// but if we calculate original fees per KB it's 354222
|
||||
act.bitcoinStd.satPerKb == 354222
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user