solution: optionally provide utxo for bitcoin balance request
This commit is contained in:
@@ -57,7 +57,7 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.emeraldpay:emerald-api:0.7.1"
|
implementation "io.emeraldpay:emerald-api:0.8.0"
|
||||||
|
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
implementation "io.grpc:grpc-stub:${grpcVersion}"
|
implementation "io.grpc:grpc-stub:${grpcVersion}"
|
||||||
|
|||||||
@@ -64,22 +64,41 @@ class TrackBitcoinAddress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestBalances(chain: Chain, api: BitcoinMultistream, addresses: List<String>): Flux<AddressBalance> {
|
fun requestBalances(chain: Chain, api: BitcoinMultistream, addresses: List<String>, includeUtxo: Boolean): Flux<AddressBalance> {
|
||||||
return Flux.fromIterable(addresses)
|
return Flux.fromIterable(addresses)
|
||||||
.map { Address(chain, it) }
|
.map { Address(chain, it) }
|
||||||
.flatMap { address ->
|
.flatMap { address ->
|
||||||
api.getReader()
|
balanceForAddress(api, address, includeUtxo)
|
||||||
.listUnspent(address.bitcoinAddress)
|
|
||||||
.map { unspents ->
|
|
||||||
getTotal(address, unspents)
|
|
||||||
}
|
|
||||||
.onErrorResume { t ->
|
|
||||||
log.error("Failed to get unspent", t)
|
|
||||||
Mono.empty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun balanceForAddress(api: BitcoinMultistream, address: Address, includeUtxo: Boolean): Mono<AddressBalance> {
|
||||||
|
return api.getReader()
|
||||||
|
.listUnspent(address.bitcoinAddress)
|
||||||
|
.map { unspent ->
|
||||||
|
totalUnspent(address, includeUtxo, unspent)
|
||||||
|
}
|
||||||
|
.onErrorResume { t ->
|
||||||
|
log.error("Failed to get unspent", t)
|
||||||
|
Mono.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun totalUnspent(address: Address, includeUtxo: Boolean, unspent: List<SimpleUnspent>): AddressBalance {
|
||||||
|
return if (unspent.isEmpty()) {
|
||||||
|
AddressBalance(address, BigInteger.ZERO)
|
||||||
|
} else {
|
||||||
|
unspent.map {
|
||||||
|
AddressBalance(
|
||||||
|
address,
|
||||||
|
BigInteger.valueOf(it.value),
|
||||||
|
if (includeUtxo) listOf(BalanceUtxo(it.txid, it.vout, it.value))
|
||||||
|
else emptyList()
|
||||||
|
)
|
||||||
|
}.reduce { a, b -> a.plus(b) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux<BlockchainOuterClass.AddressBalance> {
|
override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux<BlockchainOuterClass.AddressBalance> {
|
||||||
val chain = Chain.byId(request.asset.chainValue)
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
val upstream = multistreamHolder.getUpstream(chain)?.cast(BitcoinMultistream::class.java)
|
val upstream = multistreamHolder.getUpstream(chain)?.cast(BitcoinMultistream::class.java)
|
||||||
@@ -88,19 +107,10 @@ class TrackBitcoinAddress(
|
|||||||
if (addresses.isEmpty()) {
|
if (addresses.isEmpty()) {
|
||||||
return Flux.empty()
|
return Flux.empty()
|
||||||
}
|
}
|
||||||
return requestBalances(chain, upstream, addresses)
|
return requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||||
.map(this@TrackBitcoinAddress::buildResponse)
|
.map(this@TrackBitcoinAddress::buildResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTotal(address: Address, unspents: List<SimpleUnspent>): AddressBalance {
|
|
||||||
val total = if (unspents.isEmpty()) {
|
|
||||||
0L
|
|
||||||
} else {
|
|
||||||
unspents.map { it.value }.reduce(Long::plus)
|
|
||||||
}
|
|
||||||
return AddressBalance(address, BigInteger.valueOf(total))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun subscribe(request: BlockchainOuterClass.BalanceRequest): Flux<BlockchainOuterClass.AddressBalance> {
|
override fun subscribe(request: BlockchainOuterClass.BalanceRequest): Flux<BlockchainOuterClass.AddressBalance> {
|
||||||
val chain = Chain.byId(request.asset.chainValue)
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
@@ -110,10 +120,10 @@ class TrackBitcoinAddress(
|
|||||||
if (addresses.isEmpty()) {
|
if (addresses.isEmpty()) {
|
||||||
return Flux.empty()
|
return Flux.empty()
|
||||||
}
|
}
|
||||||
val initial = requestBalances(chain, upstream, addresses)
|
val initial = requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||||
val following = upstream.getHead().getFlux()
|
val following = upstream.getHead().getFlux()
|
||||||
.flatMap { block ->
|
.flatMap { block ->
|
||||||
requestBalances(chain, upstream, addresses)
|
requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||||
}
|
}
|
||||||
val last = HashMap<Address, BigInteger>()
|
val last = HashMap<Address, BigInteger>()
|
||||||
val result = Flux.merge(initial, following)
|
val result = Flux.merge(initial, following)
|
||||||
@@ -134,15 +144,26 @@ class TrackBitcoinAddress(
|
|||||||
.setChainValue(address.address.chain.id)
|
.setChainValue(address.address.chain.id)
|
||||||
.setCode("BTC"))
|
.setCode("BTC"))
|
||||||
.setAddress(Common.SingleAddress.newBuilder().setAddress(address.address.address))
|
.setAddress(Common.SingleAddress.newBuilder().setAddress(address.address.address))
|
||||||
|
.addAllUtxo(
|
||||||
|
address.utxo.map { utxo ->
|
||||||
|
BlockchainOuterClass.Utxo.newBuilder()
|
||||||
|
.setBalance(utxo.value.toString())
|
||||||
|
.setIndex(utxo.vout.toLong())
|
||||||
|
.setTxId(utxo.txid)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
open class AddressBalance(val address: Address, var balance: BigInteger = BigInteger.ZERO) {
|
open class AddressBalance(val address: Address, var balance: BigInteger = BigInteger.ZERO, var utxo: List<BalanceUtxo> = emptyList()) {
|
||||||
constructor(chain: Chain, address: String, balance: BigInteger) : this(Address(chain, address), balance)
|
constructor(chain: Chain, address: String, balance: BigInteger) : this(Address(chain, address), balance)
|
||||||
|
|
||||||
fun plus(other: AddressBalance) = AddressBalance(address, balance + other.balance)
|
fun plus(other: AddressBalance) = AddressBalance(address, balance + other.balance, utxo.plus(other.utxo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class BalanceUtxo(val txid: String, val vout: Int, val value: Long)
|
||||||
|
|
||||||
class Address(val chain: Chain, val address: String) {
|
class Address(val chain: Chain, val address: String) {
|
||||||
val network = if (chain == Chain.BITCOIN) {
|
val network = if (chain == Chain.BITCOIN) {
|
||||||
MainNetParams()
|
MainNetParams()
|
||||||
|
|||||||
@@ -53,27 +53,49 @@ class TrackBitcoinAddressSpec extends Specification {
|
|||||||
Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def total = track.getTotal(address, unspents)
|
def total = track.totalUnspent(address, false, unspents)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
total.balance == 100
|
total.balance == 100
|
||||||
|
total.utxo.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Correct sum for few"() {
|
def "Correct sum for few"() {
|
||||||
setup:
|
setup:
|
||||||
def unspents = [
|
def unspents = [
|
||||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L, 123L),
|
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L, 123L),
|
||||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 123L, 123L),
|
new SimpleUnspent("17d1c4adf14b222e652c58d11435fa9ee2ddea000c6f5e20e6b715eb940fc28f", 0, 123L, 123L),
|
||||||
]
|
]
|
||||||
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||||
def address = new TrackBitcoinAddress.Address(
|
def address = new TrackBitcoinAddress.Address(
|
||||||
Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def total = track.getTotal(address, unspents)
|
def total = track.totalUnspent(address, false, unspents)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
total.balance == 223
|
total.balance == 223
|
||||||
|
total.utxo.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Correct sum for few with utxo"() {
|
||||||
|
setup:
|
||||||
|
def unspents = [
|
||||||
|
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 100L, 123L),
|
||||||
|
new SimpleUnspent("17d1c4adf14b222e652c58d11435fa9ee2ddea000c6f5e20e6b715eb940fc28f", 0, 123L, 123L),
|
||||||
|
]
|
||||||
|
TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(MultistreamHolder))
|
||||||
|
def address = new TrackBitcoinAddress.Address(
|
||||||
|
Chain.BITCOIN, "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"() {
|
def "One address for single provided"() {
|
||||||
|
|||||||
Reference in New Issue
Block a user