problem: doesn't connect to Bitcoin networks over gRPC

This commit is contained in:
Igor Artamonov
2020-08-31 21:25:43 -04:00
parent 813f2ae4a2
commit ff3fc95711
22 changed files with 899 additions and 218 deletions

View File

@@ -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}")
}

View File

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

View File

@@ -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 = '''
{

View File

@@ -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:

View File

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

View File

@@ -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
}
}