solution: naive trackAddress implementation
This commit is contained in:
@@ -29,9 +29,4 @@ open class Config {
|
|||||||
return objectMapper
|
return objectMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporally hack to let Spring Boot know it has something active (i.e shouldn't shutdown, as non-web server)
|
|
||||||
@Scheduled(fixedRate = 60000)
|
|
||||||
fun readCurrentTime() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,8 @@ import java.time.Instant
|
|||||||
class BlockchainRpc(
|
class BlockchainRpc(
|
||||||
@Autowired private val nativeCall: NativeCall,
|
@Autowired private val nativeCall: NativeCall,
|
||||||
@Autowired private val streamHead: StreamHead,
|
@Autowired private val streamHead: StreamHead,
|
||||||
@Autowired private val trackTx: TrackTx
|
@Autowired private val trackTx: TrackTx,
|
||||||
|
@Autowired private val trackAddress: TrackAddress
|
||||||
): BlockchainGrpc.BlockchainImplBase() {
|
): BlockchainGrpc.BlockchainImplBase() {
|
||||||
|
|
||||||
override fun nativeCall(request: BlockchainOuterClass.CallBlockchainRequest, responseObserver: StreamObserver<BlockchainOuterClass.CallBlockchainReplyItem>) {
|
override fun nativeCall(request: BlockchainOuterClass.CallBlockchainRequest, responseObserver: StreamObserver<BlockchainOuterClass.CallBlockchainReplyItem>) {
|
||||||
@@ -35,4 +36,8 @@ class BlockchainRpc(
|
|||||||
)
|
)
|
||||||
trackTx.add(tx)
|
trackTx.add(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun trackAddress(request: BlockchainOuterClass.TrackAddressRequest, responseObserver: StreamObserver<BlockchainOuterClass.AddressStatus>) {
|
||||||
|
trackAddress.add(request, responseObserver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
158
src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackAddress.kt
Normal file
158
src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackAddress.kt
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package io.emeraldpay.dshackle.rpc
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
|
import io.emeraldpay.api.proto.Common
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import io.grpc.stub.StreamObserver
|
||||||
|
import io.infinitape.etherjar.domain.Address
|
||||||
|
import io.infinitape.etherjar.domain.Wei
|
||||||
|
import io.infinitape.etherjar.rpc.Batch
|
||||||
|
import io.infinitape.etherjar.rpc.Commands
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockTag
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.publisher.toFlux
|
||||||
|
import reactor.util.function.Tuple2
|
||||||
|
import reactor.util.function.Tuples
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
import javax.annotation.PostConstruct
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class TrackAddress(
|
||||||
|
@Autowired private val upstreams: Upstreams
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val clients = HashMap<Chain, ConcurrentLinkedQueue<TrackedAddress>>()
|
||||||
|
|
||||||
|
private val allChains = listOf(Chain.MORDEN, Chain.ETHEREUM_CLASSIC, Chain.ETHEREUM)
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
fun init() {
|
||||||
|
allChains.forEach { chain ->
|
||||||
|
clients[chain] = ConcurrentLinkedQueue()
|
||||||
|
upstreams.ethereumUpstream(chain)?.head?.let { head ->
|
||||||
|
head.getFlux().subscribe { verifyAll(chain) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 120_000)
|
||||||
|
fun pingOld() {
|
||||||
|
val period = Duration.ofMinutes(15)
|
||||||
|
allChains.forEach { chain ->
|
||||||
|
clients[chain]?.let { clients ->
|
||||||
|
clients.toFlux().filter {
|
||||||
|
it.lastPing < Instant.now().minus(period)
|
||||||
|
}.subscribe {
|
||||||
|
notify(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(request: BlockchainOuterClass.TrackAddressRequest, responseObserver: StreamObserver<BlockchainOuterClass.AddressStatus>) {
|
||||||
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
|
if (!allChains.contains(chain)) {
|
||||||
|
responseObserver.onError(Exception("Unsupported chain ${request.asset.chainValue}"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (request.asset.code?.toLowerCase() != "ether") {
|
||||||
|
responseObserver.onError(Exception("Unsupported asset ${request.asset.code}"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val new = java.util.ArrayList<TrackedAddress>()
|
||||||
|
val observer = StreamSender<BlockchainOuterClass.AddressStatus>(responseObserver)
|
||||||
|
if (request.address.addrTypeCase == Common.AnyAddress.AddrTypeCase.ADDRESS_SINGLE) {
|
||||||
|
new.add(forAddress(request.address.addressSingle, chain, observer))
|
||||||
|
} else if (request.address.addrTypeCase == Common.AnyAddress.AddrTypeCase.ADDRESS_MULTI) {
|
||||||
|
request.address.addressMulti.addressesList.forEach { address ->
|
||||||
|
new.add(forAddress(address, chain, observer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify(chain, new).subscribe()
|
||||||
|
clients[chain]?.addAll(new)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forAddress(address: Common.SingleAddress, chain: Chain, observer: StreamSender<BlockchainOuterClass.AddressStatus>): TrackedAddress {
|
||||||
|
val addressParsed = Address.from(address.address)
|
||||||
|
return TrackedAddress(
|
||||||
|
chain,
|
||||||
|
observer,
|
||||||
|
addressParsed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyAll(chain: Chain) {
|
||||||
|
clients[chain]?.let { all ->
|
||||||
|
all.toFlux()
|
||||||
|
.buffer(20)
|
||||||
|
.map { group ->
|
||||||
|
verify(chain, group).subscribe { updated -> notify(updated) }
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verify(chain: Chain, group: List<TrackedAddress>): Flux<TrackedAddress> {
|
||||||
|
val up = upstreams.ethereumUpstream(chain)!!
|
||||||
|
return group.toFlux()
|
||||||
|
.reduce<Tuple2<Batch, ArrayList<Update>>>(Tuples.of(Batch(), ArrayList())) { batch, a ->
|
||||||
|
val f = batch.t1.add(Commands.eth().getBalance(a.address, BlockTag.LATEST));
|
||||||
|
batch.t2.add(Update(a, f))
|
||||||
|
batch
|
||||||
|
}
|
||||||
|
.flatMap {
|
||||||
|
Mono.fromCompletionStage(up.api.execute(it.t1))
|
||||||
|
.thenReturn(it.t2)
|
||||||
|
}
|
||||||
|
.flatMapMany {
|
||||||
|
it.toFlux()
|
||||||
|
}
|
||||||
|
.filter {
|
||||||
|
it.addr.balance == null || it.addr.balance != it.value.get()
|
||||||
|
}
|
||||||
|
.doOnNext {
|
||||||
|
it.addr.balance = it.value.get()
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
it.addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(address: TrackedAddress): Boolean {
|
||||||
|
val sent = address.stream.send(
|
||||||
|
BlockchainOuterClass.AddressStatus.newBuilder()
|
||||||
|
.setBalance(ByteString.copyFrom(address.balance!!.amount!!.toByteArray()))
|
||||||
|
.setAsset(Common.Asset.newBuilder()
|
||||||
|
.setChainValue(address.chain.id)
|
||||||
|
.setCode("ETHER")
|
||||||
|
)
|
||||||
|
.setAddress(Common.SingleAddress.newBuilder().setAddress(address.address.toHex()))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
if (!sent) {
|
||||||
|
clients[address.chain]?.remove(address)
|
||||||
|
}
|
||||||
|
address.lastPing = Instant.now()
|
||||||
|
return sent
|
||||||
|
}
|
||||||
|
|
||||||
|
class Update(val addr: TrackedAddress, val value: Future<Wei>)
|
||||||
|
|
||||||
|
class TrackedAddress(val chain: Chain,
|
||||||
|
val stream: StreamSender<BlockchainOuterClass.AddressStatus>,
|
||||||
|
val address: Address,
|
||||||
|
val since: Instant = Instant.now(),
|
||||||
|
var lastPing: Instant = Instant.now(),
|
||||||
|
var balance: Wei? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user