solution: initial implementation for Bitcoin upstreams
This commit is contained in:
@@ -77,9 +77,9 @@ class BlocksMemCacheSpec extends Specification {
|
||||
def act3 = cache.read(BlockId.from(hash3)).block()
|
||||
def act4 = cache.read(BlockId.from(hash4)).block()
|
||||
then:
|
||||
act2.hash.toHex() == hash2
|
||||
act3.hash.toHex() == hash3
|
||||
act4.hash.toHex() == hash4
|
||||
act2.hash.toHex() == hash2.substring(2)
|
||||
act3.hash.toHex() == hash3.substring(2)
|
||||
act4.hash.toHex() == hash4.substring(2)
|
||||
act1 == null
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ class HeightCacheSpec extends Specification {
|
||||
def act3 = cache.read(102).block()
|
||||
def act4 = cache.read(103).block()
|
||||
then:
|
||||
act1.toHex() == hash1
|
||||
act2.toHex() == hash2
|
||||
act3.toHex() == hash3
|
||||
act4.toHex() == hash4
|
||||
act1.toHex() == hash1.substring(2)
|
||||
act2.toHex() == hash2.substring(2)
|
||||
act3.toHex() == hash3.substring(2)
|
||||
act4.toHex() == hash4.substring(2)
|
||||
}
|
||||
|
||||
def "Keeps only configured amount"() {
|
||||
@@ -65,8 +65,8 @@ class HeightCacheSpec extends Specification {
|
||||
def act4 = cache.read(103).block()
|
||||
then:
|
||||
act1 == null
|
||||
act2.toHex() == hash2
|
||||
act3.toHex() == hash3
|
||||
act4.toHex() == hash4
|
||||
act2.toHex() == hash2.substring(2)
|
||||
act3.toHex() == hash3.substring(2)
|
||||
act4.toHex() == hash4.substring(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ class TxMemCacheSpec extends Specification {
|
||||
def act3 = cache.read(TxId.from(hash3)).block()
|
||||
def act4 = cache.read(TxId.from(hash4)).block()
|
||||
then:
|
||||
act2.hash.toHex() == hash2
|
||||
act3.hash.toHex() == hash3
|
||||
act4.hash.toHex() == hash4
|
||||
act2.hash.toHex() == hash2.substring(2)
|
||||
act3.hash.toHex() == hash3.substring(2)
|
||||
act4.hash.toHex() == hash4.substring(2)
|
||||
act1 == null
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ class TxMemCacheSpec extends Specification {
|
||||
then:
|
||||
act1 == null
|
||||
act2 == null
|
||||
act3.hash.toHex() == hash3
|
||||
act4.hash.toHex() == hash4
|
||||
act3.hash.toHex() == hash3.substring(2)
|
||||
act4.hash.toHex() == hash4.substring(2)
|
||||
}
|
||||
|
||||
def "Evict all by block data"() {
|
||||
@@ -137,7 +137,7 @@ class TxMemCacheSpec extends Specification {
|
||||
then:
|
||||
act1 == null
|
||||
act2 == null
|
||||
act3.hash.toHex() == hash3
|
||||
act4.hash.toHex() == hash4
|
||||
act3.hash.toHex() == hash3.substring(2)
|
||||
act4.hash.toHex() == hash4.substring(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.emeraldpay.dshackle.data
|
||||
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import spock.lang.Specification
|
||||
|
||||
class BlockIdSpec extends Specification {
|
||||
|
||||
def "Create from string"() {
|
||||
expect:
|
||||
BlockId.from(source).toHex() == exp
|
||||
where:
|
||||
exp | source
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "A0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xA0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
}
|
||||
|
||||
def "Create from block hash"() {
|
||||
expect:
|
||||
BlockId.from(BlockHash.from(source)).toHex() == exp
|
||||
where:
|
||||
exp | source
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
}
|
||||
|
||||
}
|
||||
29
src/test/groovy/io/emeraldpay/dshackle/data/TxIdSpec.groovy
Normal file
29
src/test/groovy/io/emeraldpay/dshackle/data/TxIdSpec.groovy
Normal file
@@ -0,0 +1,29 @@
|
||||
package io.emeraldpay.dshackle.data
|
||||
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import spock.lang.Specification
|
||||
|
||||
class TxIdSpec extends Specification {
|
||||
|
||||
def "Create from string"() {
|
||||
expect:
|
||||
TxId.from(source).toHex() == exp
|
||||
where:
|
||||
exp | source
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "A0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xA0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
}
|
||||
|
||||
def "Create from block hash"() {
|
||||
expect:
|
||||
TxId.from(TransactionId.from(source)).toHex() == exp
|
||||
where:
|
||||
exp | source
|
||||
"a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
"00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100"
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.publisher.TopicProcessor
|
||||
|
||||
class EthereumHeadMock implements EthereumHead {
|
||||
class EthereumHeadMock implements Head {
|
||||
|
||||
private TopicProcessor<BlockContainer> bus = TopicProcessor.create()
|
||||
private BlockContainer latest
|
||||
|
||||
@@ -17,11 +17,11 @@ package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -58,12 +58,12 @@ class EthereumUpstreamMock extends EthereumUpstream {
|
||||
}
|
||||
|
||||
@Override
|
||||
EthereumHead createHead() {
|
||||
Head createHead() {
|
||||
return ethereumHeadMock
|
||||
}
|
||||
|
||||
@Override
|
||||
EthereumHead getHead() {
|
||||
Head getHead() {
|
||||
return ethereumHeadMock
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ import io.grpc.inprocess.InProcessChannelBuilder
|
||||
import io.grpc.inprocess.InProcessServerBuilder
|
||||
import io.grpc.testing.GrpcCleanupRule
|
||||
|
||||
class MockServer {
|
||||
class MockGrpcServer {
|
||||
|
||||
GrpcCleanupRule grpcCleanup = new GrpcCleanupRule()
|
||||
|
||||
ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(ReactorBlockchainGrpc.BlockchainImplBase impl){
|
||||
ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(ReactorBlockchainGrpc.BlockchainImplBase impl) {
|
||||
String serverName = InProcessServerBuilder.generateName()
|
||||
grpcCleanup.register(InProcessServerBuilder
|
||||
.forName(serverName).directExecutor().addService(impl).build().start());
|
||||
def channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build())
|
||||
def channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build())
|
||||
return ReactorBlockchainGrpc.newReactorStub(channel)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
@@ -30,7 +29,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
def "Get blockNumber from head"() {
|
||||
setup:
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
@@ -58,7 +57,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
def "Return empty if block is not cached"() {
|
||||
setup:
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
@@ -76,7 +75,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def "Return block by hash when cached"() {
|
||||
setup:
|
||||
def cache = new BlocksMemCache();
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(cache).build(),
|
||||
@@ -106,7 +105,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
setup:
|
||||
def blocksCache = new BlocksMemCache()
|
||||
def heightCache = new HeightCache()
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setBlockByHeight(heightCache).build(),
|
||||
@@ -135,7 +134,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
@@ -161,7 +160,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
@@ -191,7 +190,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
@@ -219,7 +218,7 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import org.mockserver.model.HttpRequest
|
||||
import org.mockserver.model.HttpResponse
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class BitcoinApiSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
BitcoinApi api
|
||||
|
||||
def setup() {
|
||||
mockServer = ClientAndServer.startClientAndServer(18332);
|
||||
api = new BitcoinApi(
|
||||
new BitcoinRpcClient("localhost:18332", null),
|
||||
TestingCommons.objectMapper()
|
||||
)
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
def "Request simple"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90",' +
|
||||
' "error": null,' +
|
||||
' "id": 15' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(15, "getbestblockhash", [], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"getbestblockhash","params":[],"id":15}')
|
||||
)
|
||||
}
|
||||
|
||||
def "Request with params"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": "something",' +
|
||||
' "id": 1' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(1, "getsomething", ["something", false], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("something")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"getsomething","params":["something",false],"id":1}')
|
||||
)
|
||||
}
|
||||
|
||||
def "Returns error"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": null,' +
|
||||
' "error": {' +
|
||||
' "code": -32601,' +
|
||||
' "message": "Method not found"' +
|
||||
' },' +
|
||||
' "id": 1' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(1, "geterror", [], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectError(RpcException)
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"geterror","params":[],"id":1}')
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.config.AuthConfig
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import org.mockserver.matchers.Times
|
||||
import org.mockserver.model.HttpRequest
|
||||
import org.mockserver.model.HttpResponse
|
||||
import org.mockserver.model.MediaType
|
||||
import org.mockserver.verify.VerificationTimes
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class BitcoinRpcClientSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
|
||||
def setup() {
|
||||
mockServer = ClientAndServer.startClientAndServer(18332);
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
def "Make request"() {
|
||||
setup:
|
||||
def client = new BitcoinRpcClient("localhost:18332", null)
|
||||
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping"),
|
||||
Times.exactly(1)
|
||||
).respond(
|
||||
HttpResponse.response()
|
||||
.withBody("pong")
|
||||
)
|
||||
when:
|
||||
def act = client.execute("ping".bytes).map { new String(it) }
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("pong")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping")
|
||||
.withContentType(MediaType.APPLICATION_JSON)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
def "Make request with basic auth"() {
|
||||
setup:
|
||||
def auth = new AuthConfig.ClientBasicAuth("user", "passwd")
|
||||
def client = new BitcoinRpcClient("localhost:18332", auth)
|
||||
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping")
|
||||
).respond(
|
||||
HttpResponse.response()
|
||||
.withBody("pong")
|
||||
)
|
||||
when:
|
||||
def act = client.execute("ping".bytes).map { new String(it) }
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("pong")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping")
|
||||
.withContentType(MediaType.APPLICATION_JSON)
|
||||
.withHeader("authorization", "Basic dXNlcjpwYXNzd2Q=")
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
|
||||
import de.jodamob.kotlin.testrunner.OpenedClasses
|
||||
import de.jodamob.kotlin.testrunner.SpotlinTestRunner
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
@RunWith(SpotlinTestRunner)
|
||||
@OpenedClasses(BitcoinApi)
|
||||
class BitcoinRpcHeadSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
|
||||
def "Follow 2 blocks created over 3 requests"() {
|
||||
setup:
|
||||
String hash1 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50001"
|
||||
String hash2 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50002"
|
||||
|
||||
def block1 = """
|
||||
{
|
||||
"hash": "${hash1}",
|
||||
"confirmations": 2,
|
||||
"strippedsize": 800464,
|
||||
"size": 1591897,
|
||||
"weight": 3993289,
|
||||
"height": 626472,
|
||||
"version": 549453824,
|
||||
"versionHex": "20c00000",
|
||||
"merkleroot": "7835a26b6c9316232f8194c6b64b0a366e26a9a14e6a7e7409c34594d98a83c2",
|
||||
"tx": [],
|
||||
"time": 1587171526,
|
||||
"mediantime": 1587168930,
|
||||
"nonce": 4111305375,
|
||||
"bits": "171320bc",
|
||||
"difficulty": 14715214060656.53,
|
||||
"chainwork": "00000000000000000000000000000000000000000e9baec5f63190a2b0bbcf6b",
|
||||
"nTx": 0,
|
||||
"previousblockhash": "000000000000000000106b8cb739dd3c412613aa5d459a739794ec2572a74c10"
|
||||
}
|
||||
"""
|
||||
def block2 = """
|
||||
{
|
||||
"hash": "${hash2}",
|
||||
"confirmations": 2,
|
||||
"strippedsize": 800464,
|
||||
"size": 1591897,
|
||||
"weight": 3993289,
|
||||
"height": 626473,
|
||||
"version": 549453824,
|
||||
"versionHex": "20c00000",
|
||||
"merkleroot": "7835a26b6c9316232f8194c6b64b0a366e26a9a14e6a7e7409c34594d98a83c2",
|
||||
"tx": [],
|
||||
"time": 1587172526,
|
||||
"mediantime": 1587168930,
|
||||
"nonce": 4111305375,
|
||||
"bits": "171320bc",
|
||||
"difficulty": 14715214060656.53,
|
||||
"chainwork": "00000000000000000000000000000000000000000e9baec5f63190a2b0bbcf6c",
|
||||
"nTx": 0,
|
||||
"previousblockhash": "${hash1}"
|
||||
}
|
||||
"""
|
||||
|
||||
BitcoinApi api = Mock(BitcoinApi) {
|
||||
_ * executeAndResult(_, "getbestblockhash", _, String) >>> [
|
||||
Mono.just(hash1), Mono.just(hash1), Mono.just(hash2)
|
||||
]
|
||||
_ * execute(_, "getblock", [hash1]) >> Mono.just(block1.bytes)
|
||||
_ * execute(_, "getblock", [hash2]) >> Mono.just(block2.bytes)
|
||||
}
|
||||
BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(TestingCommons.objectMapper()), Duration.ofMillis(200))
|
||||
|
||||
when:
|
||||
def act = head.flux.take(2)
|
||||
head.start()
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches { block ->
|
||||
block.hash.toHex() == hash1
|
||||
}.as("Block 1")
|
||||
.expectNextMatches { block ->
|
||||
block.hash.toHex() == hash2
|
||||
}.as("Block 2")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import spock.lang.Specification
|
||||
|
||||
class ExtractBlockSpec extends Specification {
|
||||
|
||||
ExtractBlock extractBlock = new ExtractBlock(TestingCommons.objectMapper())
|
||||
|
||||
def "Extract standard block"() {
|
||||
setup:
|
||||
def json = this.class.getClassLoader().getResourceAsStream("bitcoin/block-626472.json").bytes
|
||||
when:
|
||||
def act = extractBlock.extract(json)
|
||||
then:
|
||||
act.hash.toHex() == "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90"
|
||||
act.height == 626472
|
||||
act.timestamp.toString() == "2020-04-18T00:58:46Z"
|
||||
act.difficulty.toString(16) == "e9baec5f63190a2b0bbcf6b"
|
||||
!act.full
|
||||
act.transactions.size() == 1487
|
||||
act.json == json
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
@@ -37,10 +38,10 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
|
||||
def "Updates lag distance"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
Head master = Mock()
|
||||
|
||||
EthereumHead head1 = Mock()
|
||||
EthereumHead head2 = Mock()
|
||||
Head head1 = Mock()
|
||||
Head head2 = Mock()
|
||||
|
||||
Upstream up1 = Mock {
|
||||
_ * getHead() >> head1
|
||||
@@ -89,7 +90,7 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
|
||||
def "Probes until there is no difference"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
Head master = Mock()
|
||||
HeadLagObserver observer = new EthereumHeadLagObserver(master, [])
|
||||
Upstream up = Mock()
|
||||
|
||||
@@ -118,7 +119,7 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
|
||||
def "Correct distance"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
Head master = Mock()
|
||||
HeadLagObserver observer = new EthereumHeadLagObserver(master, [])
|
||||
expect:
|
||||
def top = new BlockJson().with {
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.emeraldpay.api.proto.BlockchainGrpc
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.MockServer
|
||||
import io.emeraldpay.dshackle.test.MockGrpcServer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -38,7 +38,7 @@ import java.util.concurrent.CompletableFuture
|
||||
|
||||
class EthereumGrpcUpstreamSpec extends Specification {
|
||||
|
||||
MockServer mockServer = new MockServer()
|
||||
MockGrpcServer mockServer = new MockGrpcServer()
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Subscribe to head"() {
|
||||
|
||||
1509
src/test/resources/bitcoin/block-626472.json
Normal file
1509
src/test/resources/bitcoin/block-626472.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user