solution: optionally provide utxo for bitcoin balance request
This commit is contained in:
@@ -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)
|
||||
.map { Address(chain, it) }
|
||||
.flatMap { address ->
|
||||
api.getReader()
|
||||
.listUnspent(address.bitcoinAddress)
|
||||
.map { unspents ->
|
||||
getTotal(address, unspents)
|
||||
}
|
||||
.onErrorResume { t ->
|
||||
log.error("Failed to get unspent", t)
|
||||
Mono.empty()
|
||||
}
|
||||
balanceForAddress(api, address, includeUtxo)
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
val chain = Chain.byId(request.asset.chainValue)
|
||||
val upstream = multistreamHolder.getUpstream(chain)?.cast(BitcoinMultistream::class.java)
|
||||
@@ -88,19 +107,10 @@ class TrackBitcoinAddress(
|
||||
if (addresses.isEmpty()) {
|
||||
return Flux.empty()
|
||||
}
|
||||
return requestBalances(chain, upstream, addresses)
|
||||
return requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||
.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> {
|
||||
val chain = Chain.byId(request.asset.chainValue)
|
||||
@@ -110,10 +120,10 @@ class TrackBitcoinAddress(
|
||||
if (addresses.isEmpty()) {
|
||||
return Flux.empty()
|
||||
}
|
||||
val initial = requestBalances(chain, upstream, addresses)
|
||||
val initial = requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||
val following = upstream.getHead().getFlux()
|
||||
.flatMap { block ->
|
||||
requestBalances(chain, upstream, addresses)
|
||||
requestBalances(chain, upstream, addresses, request.includeUtxo)
|
||||
}
|
||||
val last = HashMap<Address, BigInteger>()
|
||||
val result = Flux.merge(initial, following)
|
||||
@@ -134,15 +144,26 @@ class TrackBitcoinAddress(
|
||||
.setChainValue(address.address.chain.id)
|
||||
.setCode("BTC"))
|
||||
.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()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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) {
|
||||
val network = if (chain == Chain.BITCOIN) {
|
||||
MainNetParams()
|
||||
|
||||
@@ -53,27 +53,49 @@ class TrackBitcoinAddressSpec extends Specification {
|
||||
Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"
|
||||
)
|
||||
when:
|
||||
def total = track.getTotal(address, unspents)
|
||||
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, 123L),
|
||||
new SimpleUnspent("f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", 0, 123L, 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.getTotal(address, unspents)
|
||||
def total = track.totalUnspent(address, false, unspents)
|
||||
|
||||
then:
|
||||
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"() {
|
||||
|
||||
Reference in New Issue
Block a user