solution: naive trackAddress implementation

This commit is contained in:
Igor Artamonov
2019-06-10 23:03:29 -04:00
parent 1fa873b1df
commit be40fce6c5
3 changed files with 164 additions and 6 deletions

View File

@@ -29,9 +29,4 @@ open class Config {
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() {
}
}

View File

@@ -14,7 +14,8 @@ import java.time.Instant
class BlockchainRpc(
@Autowired private val nativeCall: NativeCall,
@Autowired private val streamHead: StreamHead,
@Autowired private val trackTx: TrackTx
@Autowired private val trackTx: TrackTx,
@Autowired private val trackAddress: TrackAddress
): BlockchainGrpc.BlockchainImplBase() {
override fun nativeCall(request: BlockchainOuterClass.CallBlockchainRequest, responseObserver: StreamObserver<BlockchainOuterClass.CallBlockchainReplyItem>) {
@@ -35,4 +36,8 @@ class BlockchainRpc(
)
trackTx.add(tx)
}
override fun trackAddress(request: BlockchainOuterClass.TrackAddressRequest, responseObserver: StreamObserver<BlockchainOuterClass.AddressStatus>) {
trackAddress.add(request, responseObserver)
}
}

View 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
)
}