solution: optionally provide utxo for bitcoin balance request

This commit is contained in:
Igor Artamonov
2020-09-15 19:17:24 -04:00
parent 56318d800e
commit 77d7ec1101
3 changed files with 71 additions and 28 deletions

View File

@@ -57,7 +57,7 @@ configurations {
}
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-stub:${grpcVersion}"

View File

@@ -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()

View File

@@ -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"() {