added signature for native subscribe
This commit is contained in:
Submodule dshackle-cli/grpc updated: 0d32b45d00...48ea13c0d1
Submodule emerald-java-client updated: a3dde262ff...b05afe6af1
@@ -31,34 +31,40 @@ class BlockContainer(
|
|||||||
json: ByteArray?,
|
json: ByteArray?,
|
||||||
val parsed: Any?,
|
val parsed: Any?,
|
||||||
val transactions: List<TxId> = emptyList(),
|
val transactions: List<TxId> = emptyList(),
|
||||||
val nodeRating: Int = 0
|
val nodeRating: Int = 0,
|
||||||
|
val upstreamId: String = ""
|
||||||
) : SourceContainer(json, parsed) {
|
) : SourceContainer(json, parsed) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun from(block: BlockJson<*>, raw: ByteArray): BlockContainer {
|
fun from(block: BlockJson<*>, raw: ByteArray, upstreamId: String): BlockContainer {
|
||||||
val hasTransactions = block.transactions?.filterIsInstance<TransactionJson>()?.count() ?: 0 > 0
|
val hasTransactions = !block.transactions?.filterIsInstance<TransactionJson>().isNullOrEmpty()
|
||||||
return BlockContainer(
|
return BlockContainer(
|
||||||
block.number,
|
height = block.number,
|
||||||
BlockId.from(block),
|
hash = BlockId.from(block),
|
||||||
block.totalDifficulty,
|
difficulty = block.totalDifficulty,
|
||||||
block.timestamp,
|
timestamp = block.timestamp,
|
||||||
hasTransactions,
|
full = hasTransactions,
|
||||||
raw,
|
json = raw,
|
||||||
block,
|
parsed = block,
|
||||||
block.transactions?.map { TxId.from(it.hash) } ?: emptyList()
|
transactions = block.transactions?.map { TxId.from(it.hash) } ?: emptyList(),
|
||||||
|
upstreamId = upstreamId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun from(block: BlockJson<*>): BlockContainer {
|
fun from(block: BlockJson<*>): BlockContainer {
|
||||||
return from(block, Global.objectMapper.writeValueAsBytes(block))
|
return from(block, "unknown")
|
||||||
|
}
|
||||||
|
@JvmStatic
|
||||||
|
fun from(block: BlockJson<*>, upstream: String): BlockContainer {
|
||||||
|
return from(block, Global.objectMapper.writeValueAsBytes(block), upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromEthereumJson(raw: ByteArray): BlockContainer {
|
fun fromEthereumJson(raw: ByteArray, upstream: String): BlockContainer {
|
||||||
val block = Global.objectMapper.readValue(raw, BlockJson::class.java)
|
val block = Global.objectMapper.readValue(raw, BlockJson::class.java)
|
||||||
return from(block, raw)
|
return from(block, raw, upstream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class QuorumRpcReader(
|
|||||||
src.map {
|
src.map {
|
||||||
val signature = response.providedSignature
|
val signature = response.providedSignature
|
||||||
?: if (key.nonce != null) {
|
?: if (key.nonce != null) {
|
||||||
signer?.sign(key.nonce, response.getResult(), api)
|
signer?.sign(key.nonce, response.getResult(), api.getId())
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ open class NativeCall(
|
|||||||
.flatMap(JsonRpcResponse::requireResult)
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
.map {
|
.map {
|
||||||
if (ctx.nonce != null) {
|
if (ctx.nonce != null) {
|
||||||
CallResult.ok(ctx.id, ctx.nonce, it, signer.sign(ctx.nonce, it, ctx.upstream))
|
CallResult.ok(ctx.id, ctx.nonce, it, signer.sign(ctx.nonce, it, ctx.upstream.getId()))
|
||||||
} else {
|
} else {
|
||||||
CallResult.ok(ctx.id, null, it, null)
|
CallResult.ok(ctx.id, null, it, null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import io.emeraldpay.dshackle.SilentException
|
|||||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.HasUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
|
||||||
import io.emeraldpay.grpc.BlockchainType
|
import io.emeraldpay.grpc.BlockchainType
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import io.grpc.Status
|
import io.grpc.Status
|
||||||
@@ -35,7 +37,8 @@ import reactor.core.publisher.Mono
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
open class NativeSubscribe(
|
open class NativeSubscribe(
|
||||||
@Autowired private val multistreamHolder: MultistreamHolder
|
@Autowired private val multistreamHolder: MultistreamHolder,
|
||||||
|
@Autowired private val signer: ResponseSigner
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -51,7 +54,7 @@ open class NativeSubscribe(
|
|||||||
.onErrorMap(this@NativeSubscribe::convertToStatus)
|
.onErrorMap(this@NativeSubscribe::convertToStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(request: BlockchainOuterClass.NativeSubscribeRequest): Publisher<out Any> {
|
fun start(request: BlockchainOuterClass.NativeSubscribeRequest): Publisher<ResponseHolder> {
|
||||||
val chain = Chain.byId(request.chainValue)
|
val chain = Chain.byId(request.chainValue)
|
||||||
if (BlockchainType.from(chain) != BlockchainType.ETHEREUM_POS && BlockchainType.from(chain) != BlockchainType.ETHEREUM) {
|
if (BlockchainType.from(chain) != BlockchainType.ETHEREUM_POS && BlockchainType.from(chain) != BlockchainType.ETHEREUM) {
|
||||||
return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}"))
|
return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}"))
|
||||||
@@ -61,7 +64,9 @@ open class NativeSubscribe(
|
|||||||
objectMapper.readValue(it.newInput(), Map::class.java)
|
objectMapper.readValue(it.newInput(), Map::class.java)
|
||||||
}
|
}
|
||||||
val matcher = Selector.convertToMatcher(request.selector)
|
val matcher = Selector.convertToMatcher(request.selector)
|
||||||
return subscribe(chain, method, params, matcher)
|
return subscribe(chain, method, params, matcher).map { resp ->
|
||||||
|
ResponseHolder(resp, request.nonce.takeIf { it != 0L })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertToStatus(t: Throwable) = when (t) {
|
fun convertToStatus(t: Throwable) = when (t) {
|
||||||
@@ -86,10 +91,43 @@ open class NativeSubscribe(
|
|||||||
.subscribe(method, params, matcher)
|
.subscribe(method, params, matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertToProto(value: Any): BlockchainOuterClass.NativeSubscribeReplyItem {
|
fun convertToProto(holder: ResponseHolder): BlockchainOuterClass.NativeSubscribeReplyItem {
|
||||||
val result = objectMapper.writeValueAsBytes(value)
|
val result = objectMapper.writeValueAsBytes(holder.response)
|
||||||
return BlockchainOuterClass.NativeSubscribeReplyItem.newBuilder()
|
val builder = BlockchainOuterClass.NativeSubscribeReplyItem.newBuilder()
|
||||||
.setPayload(ByteString.copyFrom(result))
|
.setPayload(ByteString.copyFrom(result))
|
||||||
.build()
|
|
||||||
|
holder.nonce?.also { nonce ->
|
||||||
|
holder.getSource()?.let {
|
||||||
|
signer.sign(nonce, result, it)
|
||||||
|
}?.let {
|
||||||
|
buildSignature(nonce, it)
|
||||||
|
}?.also {
|
||||||
|
builder.signature = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildSignature(
|
||||||
|
nonce: Long,
|
||||||
|
signature: ResponseSigner.Signature
|
||||||
|
): BlockchainOuterClass.NativeCallReplySignature {
|
||||||
|
val msg = BlockchainOuterClass.NativeCallReplySignature.newBuilder()
|
||||||
|
msg.signature = ByteString.copyFrom(signature.value)
|
||||||
|
msg.keyId = signature.keyId
|
||||||
|
msg.upstreamId = signature.upstreamId
|
||||||
|
msg.nonce = nonce
|
||||||
|
return msg.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ResponseHolder(
|
||||||
|
val response: Any,
|
||||||
|
val nonce: Long?
|
||||||
|
) {
|
||||||
|
fun getSource(): String? =
|
||||||
|
if (response is HasUpstream) {
|
||||||
|
response.upstreamId.takeIf { it != "unknown" }
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory
|
|||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
open class DefaultEthereumHead(
|
open class DefaultEthereumHead(
|
||||||
|
private val upstreamId: String,
|
||||||
forkChoice: ForkChoice,
|
forkChoice: ForkChoice,
|
||||||
blockValidator: BlockValidator
|
blockValidator: BlockValidator
|
||||||
) : Head, AbstractHead(forkChoice, blockValidator) {
|
) : Head, AbstractHead(forkChoice, blockValidator) {
|
||||||
@@ -57,7 +58,7 @@ open class DefaultEthereumHead(
|
|||||||
.timeout(Defaults.timeout, Mono.error(Exception("Block data not received")))
|
.timeout(Defaults.timeout, Mono.error(Exception("Block data not received")))
|
||||||
}
|
}
|
||||||
.map {
|
.map {
|
||||||
BlockContainer.fromEthereumJson(it.getResult())
|
BlockContainer.fromEthereumJson(it.getResult(), upstreamId)
|
||||||
}
|
}
|
||||||
.onErrorResume { err ->
|
.onErrorResume { err ->
|
||||||
log.debug("Failed to fetch latest block: ${err.message}")
|
log.debug("Failed to fetch latest block: ${err.message}")
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class EthereumDirectReader(
|
|||||||
if (block == null) {
|
if (block == null) {
|
||||||
Mono.empty<BlockContainer>()
|
Mono.empty<BlockContainer>()
|
||||||
} else {
|
} else {
|
||||||
Mono.just(BlockContainer.from(block, blockbytes))
|
Mono.just(BlockContainer.from(block, blockbytes, "unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.doOnNext { block ->
|
.doOnNext { block ->
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ import java.util.concurrent.Executors
|
|||||||
class EthereumRpcHead(
|
class EthereumRpcHead(
|
||||||
private val api: Reader<JsonRpcRequest, JsonRpcResponse>,
|
private val api: Reader<JsonRpcRequest, JsonRpcResponse>,
|
||||||
forkChoice: ForkChoice,
|
forkChoice: ForkChoice,
|
||||||
|
upstreamId: String,
|
||||||
blockValidator: BlockValidator,
|
blockValidator: BlockValidator,
|
||||||
private val interval: Duration = Duration.ofSeconds(10),
|
private val interval: Duration = Duration.ofSeconds(10),
|
||||||
) : DefaultEthereumHead(forkChoice, blockValidator), Lifecycle {
|
) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator), Lifecycle {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val scheduler =
|
val scheduler =
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ class EthereumWsFactory(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(upstream: DefaultUpstream?, validator: EthereumUpstreamValidator?): WsConnection {
|
fun create(id: String, upstream: DefaultUpstream?, validator: EthereumUpstreamValidator?): WsConnection {
|
||||||
require(upstream == null || upstream.getId() == id) {
|
require(upstream == null || upstream.getId() == id) {
|
||||||
"Creating instance for different upstream. ${upstream?.getId()} != id"
|
"Creating instance for different upstream. ${upstream?.getId()} != id"
|
||||||
}
|
}
|
||||||
return WsConnection(uri, origin, basicAuth, metrics, upstream, validator).also { ws ->
|
return WsConnection(id, uri, origin, basicAuth, metrics, upstream, validator).also { ws ->
|
||||||
config?.frameSize?.let {
|
config?.frameSize?.let {
|
||||||
ws.frameSize = it
|
ws.frameSize = it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ import reactor.core.publisher.Flux
|
|||||||
|
|
||||||
class EthereumWsHead(
|
class EthereumWsHead(
|
||||||
private val ws: WsConnection,
|
private val ws: WsConnection,
|
||||||
|
upstreamId: String,
|
||||||
forkChoice: ForkChoice,
|
forkChoice: ForkChoice,
|
||||||
blockValidator: BlockValidator
|
blockValidator: BlockValidator
|
||||||
) : DefaultEthereumHead(forkChoice, blockValidator), Lifecycle {
|
) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator), Lifecycle {
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(EthereumWsHead::class.java)
|
private val log = LoggerFactory.getLogger(EthereumWsHead::class.java)
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
open class WsConnection(
|
open class WsConnection(
|
||||||
|
private val id: String,
|
||||||
private val uri: URI,
|
private val uri: URI,
|
||||||
private val origin: URI,
|
private val origin: URI,
|
||||||
private val basicAuth: AuthConfig.ClientBasicAuth?,
|
private val basicAuth: AuthConfig.ClientBasicAuth?,
|
||||||
@@ -352,7 +353,7 @@ open class WsConnection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
.map { BlockContainer.fromEthereumJson(it) }
|
.map { BlockContainer.fromEthereumJson(it, id) }
|
||||||
.subscribeOn(Schedulers.boundedElastic())
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.timeout(Defaults.timeoutInternal, Mono.empty())
|
.timeout(Defaults.timeoutInternal, Mono.empty())
|
||||||
}.repeatWhenEmpty { n ->
|
}.repeatWhenEmpty { n ->
|
||||||
@@ -368,7 +369,7 @@ open class WsConnection(
|
|||||||
.then()
|
.then()
|
||||||
} else {
|
} else {
|
||||||
Mono.fromCallable {
|
Mono.fromCallable {
|
||||||
blocks.tryEmitNext(BlockContainer.from(block))
|
blocks.tryEmitNext(BlockContainer.from(block, id))
|
||||||
}.then()
|
}.then()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ open class EthereumConnectorFactory(
|
|||||||
private val forkChoice: ForkChoice,
|
private val forkChoice: ForkChoice,
|
||||||
private val blockValidator: BlockValidator
|
private val blockValidator: BlockValidator
|
||||||
) : ConnectorFactory {
|
) : ConnectorFactory {
|
||||||
private val log = LoggerFactory.getLogger(EthereumConnectorFactory::class.java)
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(EthereumConnectorFactory::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (preferHttp && httpFactory == null) {
|
if (preferHttp && httpFactory == null) {
|
||||||
@@ -27,7 +29,7 @@ open class EthereumConnectorFactory(
|
|||||||
|
|
||||||
override fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector {
|
override fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector {
|
||||||
if (wsFactory != null && !preferHttp) {
|
if (wsFactory != null && !preferHttp) {
|
||||||
return EthereumWsConnector(wsFactory, upstream, validator, chain, forkChoice, blockValidator)
|
return EthereumWsConnector(wsFactory, upstream, validator, forkChoice, blockValidator)
|
||||||
}
|
}
|
||||||
if (httpFactory == null) {
|
if (httpFactory == null) {
|
||||||
throw java.lang.IllegalArgumentException("Can't create rpc connector if no http factory set")
|
throw java.lang.IllegalArgumentException("Can't create rpc connector if no http factory set")
|
||||||
|
|||||||
@@ -34,15 +34,15 @@ class EthereumRpcConnector(
|
|||||||
init {
|
init {
|
||||||
if (wsFactory != null) {
|
if (wsFactory != null) {
|
||||||
// do not set upstream to the WS, since it doesn't control the RPC upstream
|
// do not set upstream to the WS, since it doesn't control the RPC upstream
|
||||||
conn = wsFactory.create(null, null)
|
conn = wsFactory.create(id, null, null)
|
||||||
val wsHead = EthereumWsHead(conn, forkChoice, blockValidator)
|
val wsHead = EthereumWsHead(conn, id, forkChoice, blockValidator)
|
||||||
// receive bew blocks through WebSockets, but also periodically verify with RPC in case if WS failed
|
// receive bew blocks through WebSockets, but also periodically verify with RPC in case if WS failed
|
||||||
val rpcHead = EthereumRpcHead(directReader, forkChoice, blockValidator, Duration.ofSeconds(60))
|
val rpcHead = EthereumRpcHead(directReader, forkChoice, id, blockValidator, Duration.ofSeconds(60))
|
||||||
head = MergedHead(listOf(rpcHead, wsHead), forkChoice)
|
head = MergedHead(listOf(rpcHead, wsHead), forkChoice)
|
||||||
} else {
|
} else {
|
||||||
conn = null
|
conn = null
|
||||||
log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC")
|
log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC")
|
||||||
head = EthereumRpcHead(directReader, forkChoice, blockValidator)
|
head = EthereumRpcHead(directReader, forkChoice, id, blockValidator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class EthereumWsConnector(
|
|||||||
wsFactory: EthereumWsFactory,
|
wsFactory: EthereumWsFactory,
|
||||||
upstream: DefaultUpstream,
|
upstream: DefaultUpstream,
|
||||||
validator: EthereumUpstreamValidator,
|
validator: EthereumUpstreamValidator,
|
||||||
chain: Chain,
|
|
||||||
forkChoice: ForkChoice,
|
forkChoice: ForkChoice,
|
||||||
blockValidator: BlockValidator
|
blockValidator: BlockValidator
|
||||||
) : EthereumConnector {
|
) : EthereumConnector {
|
||||||
@@ -24,8 +23,8 @@ class EthereumWsConnector(
|
|||||||
private val head: EthereumWsHead
|
private val head: EthereumWsHead
|
||||||
|
|
||||||
init {
|
init {
|
||||||
conn = wsFactory.create(upstream, validator)
|
conn = wsFactory.create(upstream.getId(), upstream, validator)
|
||||||
head = EthereumWsHead(conn, forkChoice, blockValidator)
|
head = EthereumWsHead(conn, upstream.getId(), forkChoice, blockValidator)
|
||||||
api = JsonRpcWsClient(conn)
|
api = JsonRpcWsClient(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class ConnectBlockUpdates(
|
|||||||
val prev = findPrevious(block)
|
val prev = findPrevious(block)
|
||||||
remember(block)
|
remember(block)
|
||||||
val removed = if (prev != null) {
|
val removed = if (prev != null) {
|
||||||
whenReplaced(prev)
|
whenReplaced(prev, block.upstreamId)
|
||||||
} else {
|
} else {
|
||||||
Flux.empty()
|
Flux.empty()
|
||||||
}
|
}
|
||||||
@@ -103,13 +103,14 @@ class ConnectBlockUpdates(
|
|||||||
/**
|
/**
|
||||||
* Produce updates for transactions when a block is replaces with a different one on the same height.
|
* Produce updates for transactions when a block is replaces with a different one on the same height.
|
||||||
*/
|
*/
|
||||||
fun whenReplaced(prev: BlockContainer): Flux<Update> {
|
fun whenReplaced(prev: BlockContainer, source: String): Flux<Update> {
|
||||||
return Flux.fromIterable(prev.transactions).map {
|
return Flux.fromIterable(prev.transactions).map {
|
||||||
Update(
|
Update(
|
||||||
prev.hash,
|
prev.hash,
|
||||||
prev.height,
|
prev.height,
|
||||||
UpdateType.DROP,
|
UpdateType.DROP,
|
||||||
it
|
it,
|
||||||
|
source
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +122,8 @@ class ConnectBlockUpdates(
|
|||||||
block.hash,
|
block.hash,
|
||||||
block.height,
|
block.height,
|
||||||
UpdateType.NEW,
|
UpdateType.NEW,
|
||||||
it
|
it,
|
||||||
|
block.upstreamId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,6 +133,7 @@ class ConnectBlockUpdates(
|
|||||||
val blockNumber: Long,
|
val blockNumber: Long,
|
||||||
val type: UpdateType,
|
val type: UpdateType,
|
||||||
val transactionId: TxId,
|
val transactionId: TxId,
|
||||||
|
val upstreamId: String
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class UpdateType {
|
enum class UpdateType {
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ class ProduceLogs(
|
|||||||
txlog.topics,
|
txlog.topics,
|
||||||
txlog.transactionHash,
|
txlog.transactionHash,
|
||||||
txlog.transactionIndex,
|
txlog.transactionIndex,
|
||||||
false
|
false,
|
||||||
|
update.upstreamId
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
oldMessages.put(LogReference(update.blockHash, update.transactionId), messages)
|
oldMessages.put(LogReference(update.blockHash, update.transactionId), messages)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHeadMessage
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHeadMessage
|
||||||
import io.emeraldpay.etherjar.rpc.json.BlockJson
|
import io.emeraldpay.etherjar.rpc.json.BlockJson
|
||||||
@@ -41,13 +42,7 @@ class ProduceNewHeads(
|
|||||||
fun start(): Flux<NewHeadMessage> {
|
fun start(): Flux<NewHeadMessage> {
|
||||||
return head.getFlux()
|
return head.getFlux()
|
||||||
.map {
|
.map {
|
||||||
if (it.parsed != null) {
|
val block = extractBlock(it)
|
||||||
it.parsed as BlockJson<TransactionRefJson>
|
|
||||||
} else {
|
|
||||||
objectMapper.readValue(it.json, BlockJson::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map { block ->
|
|
||||||
NewHeadMessage(
|
NewHeadMessage(
|
||||||
block.number,
|
block.number,
|
||||||
block.hash,
|
block.hash,
|
||||||
@@ -58,8 +53,16 @@ class ProduceNewHeads(
|
|||||||
block.gasUsed,
|
block.gasUsed,
|
||||||
block.logsBloom,
|
block.logsBloom,
|
||||||
block.miner,
|
block.miner,
|
||||||
block.baseFeePerGas?.amount
|
block.baseFeePerGas?.amount,
|
||||||
|
it.upstreamId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractBlock(blockContainer: BlockContainer): BlockJson<out TransactionRefJson> =
|
||||||
|
if (blockContainer.parsed != null) {
|
||||||
|
blockContainer.parsed as BlockJson<TransactionRefJson>
|
||||||
|
} else {
|
||||||
|
objectMapper.readValue(blockContainer.json, BlockJson::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||||
|
|
||||||
|
interface HasUpstream {
|
||||||
|
val upstreamId: String
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
@@ -40,5 +41,7 @@ data class LogMessage(
|
|||||||
val transactionHash: TransactionId,
|
val transactionHash: TransactionId,
|
||||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||||
val transactionIndex: Long,
|
val transactionIndex: Long,
|
||||||
val removed: Boolean
|
val removed: Boolean,
|
||||||
)
|
@get:JsonIgnore
|
||||||
|
override val upstreamId: String
|
||||||
|
) : HasUpstream
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
@@ -50,5 +51,7 @@ data class NewHeadMessage(
|
|||||||
val miner: Address,
|
val miner: Address,
|
||||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||||
@get:JsonInclude(JsonInclude.Include.NON_NULL)
|
@get:JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
val baseFeePerGas: BigInteger?
|
val baseFeePerGas: BigInteger?,
|
||||||
)
|
@get:JsonIgnore
|
||||||
|
override val upstreamId: String
|
||||||
|
) : HasUpstream
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ open class EthereumGrpcUpstream(
|
|||||||
defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false)))
|
defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false)))
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
.map {
|
.map {
|
||||||
BlockContainer.fromEthereumJson(it)
|
BlockContainer.fromEthereumJson(it, getId())
|
||||||
}
|
}
|
||||||
.timeout(timeout, Mono.error(TimeoutException("Timeout from upstream")))
|
.timeout(timeout, Mono.error(TimeoutException("Timeout from upstream")))
|
||||||
.doOnError { t ->
|
.doOnError { t ->
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ open class EthereumPosGrpcUpstream(
|
|||||||
defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false)))
|
defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false)))
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
.map {
|
.map {
|
||||||
BlockContainer.fromEthereumJson(it)
|
BlockContainer.fromEthereumJson(it, getId())
|
||||||
}
|
}
|
||||||
.timeout(timeout, Mono.error(TimeoutException("Timeout from upstream")))
|
.timeout(timeout, Mono.error(TimeoutException("Timeout from upstream")))
|
||||||
.doOnError { t ->
|
.doOnError { t ->
|
||||||
|
|||||||
@@ -17,15 +17,13 @@ class EcdsaSigner(
|
|||||||
const val MSG_SEPARATOR = '/'
|
const val MSG_SEPARATOR = '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sign(nonce: Long, message: ByteArray, source: Upstream): ResponseSigner.Signature {
|
override fun sign(nonce: Long, message: ByteArray, source: String): ResponseSigner.Signature {
|
||||||
val sig = Signature.getInstance(SIGN_SCHEME)
|
val sig = Signature.getInstance(SIGN_SCHEME)
|
||||||
sig.initSign(privateKey)
|
sig.initSign(privateKey)
|
||||||
val wrapped = wrapMessage(nonce, message, source)
|
val wrapped = wrapMessage(nonce, message, source)
|
||||||
sig.update(wrapped.toByteArray())
|
sig.update(wrapped.toByteArray())
|
||||||
val value = sig.sign()
|
val value = sig.sign()
|
||||||
return ResponseSigner.Signature(
|
return ResponseSigner.Signature(value, source, keyId)
|
||||||
value, source.getId(), keyId
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +40,7 @@ class EcdsaSigner(
|
|||||||
* - second is the nonce value encode as decimal string
|
* - second is the nonce value encode as decimal string
|
||||||
* - third is SHA256 hash of the original message encoded as hex string
|
* - third is SHA256 hash of the original message encoded as hex string
|
||||||
*/
|
*/
|
||||||
fun wrapMessage(nonce: Long, message: ByteArray, source: Upstream): String {
|
fun wrapMessage(nonce: Long, message: ByteArray, source: String): String {
|
||||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
// we create it with max capacity that we expect for the result, which is total lengths of its parts
|
// we create it with max capacity that we expect for the result, which is total lengths of its parts
|
||||||
val formatterMsg = StringBuilder(11 + 1 + 18 + 1 + 64 + 1 + 64)
|
val formatterMsg = StringBuilder(11 + 1 + 18 + 1 + 64 + 1 + 64)
|
||||||
@@ -52,7 +50,7 @@ class EcdsaSigner(
|
|||||||
.append(MSG_SEPARATOR)
|
.append(MSG_SEPARATOR)
|
||||||
// We expect that the id is short enough (less than 64 symbols) and also it doesn't contain the `/` symbol
|
// We expect that the id is short enough (less than 64 symbols) and also it doesn't contain the `/` symbol
|
||||||
// which is verified in UpstreamConfigReader and DefaultUpstream constructor
|
// which is verified in UpstreamConfigReader and DefaultUpstream constructor
|
||||||
.append(source.getId())
|
.append(source)
|
||||||
.append(MSG_SEPARATOR)
|
.append(MSG_SEPARATOR)
|
||||||
.append(Hex.encodeHexString(sha256.digest(message)))
|
.append(Hex.encodeHexString(sha256.digest(message)))
|
||||||
return formatterMsg.toString()
|
return formatterMsg.toString()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package io.emeraldpay.dshackle.upstream.signature
|
|||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
|
||||||
class NoSigner : ResponseSigner {
|
class NoSigner : ResponseSigner {
|
||||||
override fun sign(nonce: Long, message: ByteArray, source: Upstream): ResponseSigner.Signature? {
|
override fun sign(nonce: Long, message: ByteArray, source: String): ResponseSigner.Signature? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import io.emeraldpay.dshackle.upstream.Upstream
|
|||||||
|
|
||||||
interface ResponseSigner {
|
interface ResponseSigner {
|
||||||
|
|
||||||
fun sign(nonce: Long, message: ByteArray, source: Upstream): Signature?
|
fun sign(nonce: Long, message: ByteArray, source: String): Signature?
|
||||||
|
|
||||||
data class Signature(
|
data class Signature(
|
||||||
val value: ByteArray,
|
val value: ByteArray,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class EcdsaSignerSpec extends Specification {
|
|||||||
def signer = new EcdsaSigner((pair.getPrivate() as ECPrivateKey), 100L)
|
def signer = new EcdsaSigner((pair.getPrivate() as ECPrivateKey), 100L)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def sig = signer.sign(10, result, up)
|
def sig = signer.sign(10, result, up.id)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
verifier.verify(sig.value)
|
verifier.verify(sig.value)
|
||||||
@@ -148,7 +148,7 @@ class EcdsaSignerSpec extends Specification {
|
|||||||
def signer = factory.getObject() as EcdsaSigner
|
def signer = factory.getObject() as EcdsaSigner
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def sig = signer.sign(10, result, up)
|
def sig = signer.sign(10, result, up.id)
|
||||||
println("Signature: ${Hex.encodeHexString(sig.value)}")
|
println("Signature: ${Hex.encodeHexString(sig.value)}")
|
||||||
|
|
||||||
then:
|
then:
|
||||||
|
|||||||
Reference in New Issue
Block a user