problem: doesn't connect to Bitcoin networks over gRPC
This commit is contained in:
@@ -21,7 +21,7 @@ import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.upstream.Multistream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcUpstream
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
@@ -55,8 +55,8 @@ class MultistreamHolderMock implements MultistreamHolder {
|
||||
} else if (BlockchainType.fromBlockchain(chain) == BlockchainType.BITCOIN) {
|
||||
if (up instanceof BitcoinMultistream) {
|
||||
upstreams[chain] = up
|
||||
} else if (up instanceof BitcoinUpstream) {
|
||||
upstreams[chain] = new BitcoinMultistream(chain, [up as BitcoinUpstream], Caches.default())
|
||||
} else if (up instanceof BitcoinRpcUpstream) {
|
||||
upstreams[chain] = new BitcoinMultistream(chain, [up as BitcoinRpcUpstream], Caches.default())
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported upstream type ${up.class}")
|
||||
}
|
||||
|
||||
@@ -34,8 +34,12 @@ import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.time.temporal.TemporalUnit
|
||||
|
||||
class TestingCommons {
|
||||
|
||||
@@ -86,9 +90,27 @@ class TestingCommons {
|
||||
setNumber(height)
|
||||
setHash(BlockHash.from("0xc4b01774e426325b50f0c709753ec7cf1f1774439d587dfb91f2a4eeb8179cde"))
|
||||
setTotalDifficulty(BigInteger.ONE)
|
||||
setTimestamp(Instant.now())
|
||||
setTimestamp(predictableTimestamp(height, 14))
|
||||
}
|
||||
return BlockContainer.from(block)
|
||||
}
|
||||
|
||||
static BlockContainer blockForBitcoin(Long height) {
|
||||
return new BlockContainer(
|
||||
height,
|
||||
BlockId.from(StringUtils.leftPad(height.toString(), 64, "0")),
|
||||
BigInteger.valueOf(height),
|
||||
predictableTimestamp(height, 60),
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
static Instant predictableTimestamp(Long x, int stepSeconds) {
|
||||
//start from 1 Jan 2020
|
||||
Instant.ofEpochSecond(1577876400)
|
||||
.plusSeconds(x * stepSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.test.ReaderMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
@@ -258,7 +256,7 @@ class EthereumFullBlocksReaderSpec extends Specification {
|
||||
"extraField2": "extraValue2"
|
||||
}
|
||||
'''
|
||||
blocks.add(BlockContainer.from(blockJson.bytes))
|
||||
blocks.add(BlockContainer.fromEthereumJson(blockJson.bytes))
|
||||
|
||||
def tx1 = '''
|
||||
{
|
||||
|
||||
@@ -75,7 +75,8 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
})
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, new JsonRpcGrpcClient(client, chain))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
upstream.update(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
.build())
|
||||
when:
|
||||
@@ -132,7 +133,8 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
})
|
||||
def upstream = new EthereumGrpcUpstream("test", Chain.ETHEREUM, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
upstream.update(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
.build())
|
||||
when:
|
||||
@@ -193,7 +195,8 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
})
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, new JsonRpcGrpcClient(client, chain))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
upstream.update(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
.build())
|
||||
when:
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2020 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.grpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainGrpc
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.MockGrpcServer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.grpc.stub.StreamObserver
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.util.function.Function
|
||||
|
||||
class GrpcHeadSpec extends Specification {
|
||||
|
||||
MockGrpcServer mockServer = new MockGrpcServer()
|
||||
|
||||
def "Subscribe to remote"() {
|
||||
setup:
|
||||
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
|
||||
@Override
|
||||
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
|
||||
if (request.type.number != Chain.BITCOIN.id) {
|
||||
responseObserver.onError(new IllegalStateException("Unsupported chain"))
|
||||
return
|
||||
}
|
||||
[10, 11, 12, 14].forEach { height ->
|
||||
responseObserver.onNext(
|
||||
BlockchainOuterClass.ChainHead.newBuilder()
|
||||
.setChain(request.type)
|
||||
.setHeight(height)
|
||||
.build()
|
||||
)
|
||||
Thread.sleep(100)
|
||||
}
|
||||
responseObserver.onCompleted()
|
||||
}
|
||||
})
|
||||
def convert = { BlockchainOuterClass.ChainHead head ->
|
||||
TestingCommons.blockForBitcoin(head.height)
|
||||
}
|
||||
def head = new GrpcHead(
|
||||
Chain.BITCOIN,
|
||||
Stub(DefaultUpstream),
|
||||
convert, null
|
||||
)
|
||||
when:
|
||||
def act = head.getFlux()
|
||||
.take(3)
|
||||
head.start(client)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext(TestingCommons.blockForBitcoin(10))
|
||||
.expectNext(TestingCommons.blockForBitcoin(11))
|
||||
.expectNext(TestingCommons.blockForBitcoin(12))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5))
|
||||
}
|
||||
|
||||
def "Reconnect to remote on error"() {
|
||||
setup:
|
||||
int phase = 0
|
||||
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
|
||||
@Override
|
||||
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
|
||||
if (request.type.number != Chain.BITCOIN.id) {
|
||||
responseObserver.onError(new IllegalStateException("Unsupported chain"))
|
||||
return
|
||||
}
|
||||
phase++
|
||||
if (phase == 1) {
|
||||
responseObserver.onError(new RuntimeException("Phase 1 error"))
|
||||
} else if (phase == 2) {
|
||||
[10, 11].forEach { height ->
|
||||
responseObserver.onNext(
|
||||
BlockchainOuterClass.ChainHead.newBuilder()
|
||||
.setChain(request.type)
|
||||
.setHeight(height)
|
||||
.build()
|
||||
)
|
||||
Thread.sleep(100)
|
||||
}
|
||||
responseObserver.onError(new RuntimeException("Phase 2 error"))
|
||||
} else if (phase == 3) {
|
||||
[11, 12, 13].forEach { height ->
|
||||
responseObserver.onNext(
|
||||
BlockchainOuterClass.ChainHead.newBuilder()
|
||||
.setChain(request.type)
|
||||
.setHeight(height)
|
||||
.build()
|
||||
)
|
||||
Thread.sleep(100)
|
||||
}
|
||||
responseObserver.onCompleted()
|
||||
}
|
||||
}
|
||||
})
|
||||
def convert = { BlockchainOuterClass.ChainHead head ->
|
||||
TestingCommons.blockForBitcoin(head.height)
|
||||
}
|
||||
def head = new GrpcHead(
|
||||
Chain.BITCOIN,
|
||||
Stub(DefaultUpstream),
|
||||
convert, null
|
||||
)
|
||||
when:
|
||||
def act = head.getFlux()
|
||||
.take(3)
|
||||
head.start(client)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext(TestingCommons.blockForBitcoin(10))
|
||||
.expectNext(TestingCommons.blockForBitcoin(11))
|
||||
.expectNext(TestingCommons.blockForBitcoin(12))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2020 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.grpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import spock.lang.Specification
|
||||
|
||||
class GrpcUpstreamStatusSpec extends Specification {
|
||||
|
||||
def "Updates with new labels"() {
|
||||
setup:
|
||||
def status = new GrpcUpstreamStatus()
|
||||
when:
|
||||
status.update(
|
||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addNodes(
|
||||
BlockchainOuterClass.NodeDetails.newBuilder()
|
||||
.setQuorum(1)
|
||||
.addLabels(
|
||||
BlockchainOuterClass.Label.newBuilder().setName("test").setValue("foo")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
def act = status.getLabels()
|
||||
then:
|
||||
act.toList() == [
|
||||
UpstreamsConfig.Labels.fromMap([test: "foo"])
|
||||
]
|
||||
|
||||
// replace with new value
|
||||
when:
|
||||
status.update(
|
||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addNodes(
|
||||
BlockchainOuterClass.NodeDetails.newBuilder()
|
||||
.setQuorum(1)
|
||||
.addLabels(
|
||||
BlockchainOuterClass.Label.newBuilder().setName("test").setValue("bar")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
act = status.getLabels()
|
||||
then:
|
||||
act.toList() == [
|
||||
UpstreamsConfig.Labels.fromMap([test: "bar"])
|
||||
]
|
||||
|
||||
// more values
|
||||
when:
|
||||
status.update(
|
||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addNodes(
|
||||
BlockchainOuterClass.NodeDetails.newBuilder()
|
||||
.setQuorum(1)
|
||||
.addLabels(
|
||||
BlockchainOuterClass.Label.newBuilder().setName("test1").setValue("bar")
|
||||
)
|
||||
.addLabels(
|
||||
BlockchainOuterClass.Label.newBuilder().setName("test2").setValue("baz")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
act = status.getLabels()
|
||||
then:
|
||||
act.toList() == [
|
||||
UpstreamsConfig.Labels.fromMap([test1: "bar", test2: "baz"])
|
||||
]
|
||||
}
|
||||
|
||||
def "Updates with new nodes"() {
|
||||
setup:
|
||||
def status = new GrpcUpstreamStatus()
|
||||
when:
|
||||
status.update(
|
||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addNodes(
|
||||
BlockchainOuterClass.NodeDetails.newBuilder()
|
||||
.setQuorum(1)
|
||||
.addLabels(
|
||||
BlockchainOuterClass.Label.newBuilder().setName("test").setValue("foo")
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
def act = status.getNodes()
|
||||
then:
|
||||
act == new QuorumForLabels().tap {
|
||||
it.add(new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap([test: "foo"])))
|
||||
}
|
||||
}
|
||||
|
||||
def "Updates with methods"() {
|
||||
setup:
|
||||
def status = new GrpcUpstreamStatus()
|
||||
when:
|
||||
status.update(
|
||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods([
|
||||
"test_1",
|
||||
"test_2"
|
||||
])
|
||||
.build()
|
||||
)
|
||||
def act = status.getCallMethods()
|
||||
then:
|
||||
act.supportedMethods == ["test_1", "test_2"].toSet()
|
||||
act instanceof DirectCallMethods
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user