solution: basic implementation for Proxy endpoint
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
class ProxyConfigReaderSpec extends Specification {
|
||||
|
||||
ProxyConfigReader reader = new ProxyConfigReader()
|
||||
|
||||
def "Read basic proxy config"() {
|
||||
setup:
|
||||
def config = this.class.getClassLoader().getResourceAsStream("dshackle-proxy-basic.yaml")
|
||||
when:
|
||||
def act = reader.read(config)
|
||||
|
||||
then:
|
||||
act.enabled
|
||||
act.port == 8080
|
||||
act.host == '127.0.0.1'
|
||||
act.routes.size() == 1
|
||||
with(act.routes[0]) {
|
||||
id == "ethereum"
|
||||
blockchain == Chain.ETHEREUM
|
||||
}
|
||||
}
|
||||
|
||||
def "Read proxy config with two elements"() {
|
||||
setup:
|
||||
def config = this.class.getClassLoader().getResourceAsStream("dshackle-proxy-two.yaml")
|
||||
when:
|
||||
def act = reader.read(config)
|
||||
|
||||
then:
|
||||
act.enabled
|
||||
act.port == 8080
|
||||
act.routes.size() == 2
|
||||
with(act.routes[0]) {
|
||||
id == "ethereum"
|
||||
blockchain == Chain.ETHEREUM
|
||||
}
|
||||
with(act.routes[1]) {
|
||||
id == "classic"
|
||||
blockchain == Chain.ETHEREUM_CLASSIC
|
||||
}
|
||||
}
|
||||
|
||||
def "Read max proxy config"() {
|
||||
setup:
|
||||
def config = this.class.getClassLoader().getResourceAsStream("dshackle-proxy-max.yaml")
|
||||
when:
|
||||
def act = reader.read(config)
|
||||
|
||||
then:
|
||||
act.enabled
|
||||
act.host == '0.0.0.0'
|
||||
act.port == 8080
|
||||
act.routes.size() == 2
|
||||
with(act.routes[0]) {
|
||||
id == "ethereum"
|
||||
blockchain == Chain.ETHEREUM
|
||||
}
|
||||
with(act.routes[1]) {
|
||||
id == "classic"
|
||||
blockchain == Chain.ETHEREUM_CLASSIC
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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.proxy
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.config.ProxyConfig
|
||||
import io.emeraldpay.dshackle.rpc.NativeCall
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.util.function.Function
|
||||
|
||||
class ProxyServerSpec extends Specification {
|
||||
|
||||
def "Uses NativeCall"() {
|
||||
setup:
|
||||
NativeCall nativeCall = Mock(NativeCall)
|
||||
def predefined = { a -> Flux.just("hello") } as Function
|
||||
|
||||
WriteRpcJson writeRpcJson = Mock {
|
||||
1 * toJsons(_) >> predefined
|
||||
}
|
||||
|
||||
ProxyServer server = new ProxyServer(
|
||||
new ProxyConfig(),
|
||||
new ReadRpcJson(TestingCommons.objectMapper()),
|
||||
writeRpcJson,
|
||||
nativeCall
|
||||
)
|
||||
|
||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||
call.ids[1] = 1
|
||||
call.items.add(
|
||||
BlockchainOuterClass.NativeCallItem.newBuilder()
|
||||
.setMethod("eth_hello")
|
||||
.build()
|
||||
)
|
||||
when:
|
||||
def act = server.execute(Common.ChainRef.CHAIN_ETHEREUM, call)
|
||||
|
||||
then:
|
||||
1 * nativeCall.nativeCall(_) >> Flux.just(BlockchainOuterClass.NativeCallReplyItem.newBuilder().build())
|
||||
StepVerifier.create(act)
|
||||
.expectNext("hello")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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.proxy
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import spock.lang.Specification
|
||||
|
||||
class ReadRpcJsonSpec extends Specification {
|
||||
|
||||
ReadRpcJson reader = new ReadRpcJson(TestingCommons.objectMapper())
|
||||
|
||||
def "Get first symbol"() {
|
||||
expect:
|
||||
reader.getStartOfJson(input.bytes) == exp.bytes[0]
|
||||
where:
|
||||
exp | input
|
||||
"{" | "{}"
|
||||
"{" | " { }"
|
||||
"{" | "\n\n { }"
|
||||
"[" | " [ { } ] "
|
||||
}
|
||||
|
||||
def "Error for input with many spaces"() {
|
||||
setup:
|
||||
def empty = " " * 1000
|
||||
when:
|
||||
reader.getStartOfJson((empty + "{}").bytes)
|
||||
then:
|
||||
thrown(IllegalArgumentException)
|
||||
}
|
||||
|
||||
def "Error for empty spaces"() {
|
||||
when:
|
||||
reader.getStartOfJson("".bytes)
|
||||
then:
|
||||
thrown(IllegalArgumentException)
|
||||
}
|
||||
|
||||
def "Get type"() {
|
||||
expect:
|
||||
reader.getType(input.bytes) == exp
|
||||
where:
|
||||
exp | input
|
||||
ProxyCall.RpcType.SINGLE | "{}"
|
||||
ProxyCall.RpcType.SINGLE | " { }"
|
||||
ProxyCall.RpcType.SINGLE | "\n\n { }"
|
||||
ProxyCall.RpcType.BATCH | " [ { } ] "
|
||||
}
|
||||
|
||||
def "Error type for invalid input"() {
|
||||
when:
|
||||
reader.getType("hello".bytes)
|
||||
then:
|
||||
thrown(RpcException)
|
||||
|
||||
when:
|
||||
reader.getType("1".bytes)
|
||||
then:
|
||||
thrown(RpcException)
|
||||
|
||||
when:
|
||||
reader.getType("".bytes)
|
||||
then:
|
||||
thrown(RpcException)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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.proxy
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import reactor.core.publisher.Flux
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class WriteRpcJsonSpec extends Specification {
|
||||
|
||||
WriteRpcJson writer = new WriteRpcJson(TestingCommons.objectMapper())
|
||||
|
||||
def "Write empty array"() {
|
||||
when:
|
||||
def act = Flux.empty().transform(writer.asArray())
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
.join("")
|
||||
then:
|
||||
act == "[]"
|
||||
}
|
||||
|
||||
def "Write single item array"() {
|
||||
when:
|
||||
def act = Flux.just('{"id": 1}').transform(writer.asArray())
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
.join("")
|
||||
then:
|
||||
act == '[{"id": 1}]'
|
||||
}
|
||||
|
||||
def "Write two item array"() {
|
||||
setup:
|
||||
def data = [
|
||||
'{"id": 1}',
|
||||
'{"id": 2}',
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data).transform(writer.asArray())
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
.join("")
|
||||
then:
|
||||
act == '[{"id": 1},{"id": 2}]'
|
||||
}
|
||||
|
||||
def "Write few items array"() {
|
||||
setup:
|
||||
def data = [
|
||||
'{"id": 1}',
|
||||
'{"id": 2, "foo": "bar"}',
|
||||
'{"id": 3, "foo": "baz"}',
|
||||
'{"id": 4}',
|
||||
'{"id": 5, "x": 5}',
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data).transform(writer.asArray())
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
.join("")
|
||||
then:
|
||||
act == '[{"id": 1},{"id": 2, "foo": "bar"},{"id": 3, "foo": "baz"},{"id": 4},{"id": 5, "x": 5}]'
|
||||
}
|
||||
|
||||
def "Convert basic to JSON"() {
|
||||
setup:
|
||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||
call.ids[1] = "aaa"
|
||||
def data = [
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(1)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom('{"jsonrpc": "2.0", "id": 1, "result": "0x98dbb1"}', 'UTF-8'))
|
||||
.build()
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data)
|
||||
.transform(writer.toJsons(call))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == ['{"jsonrpc":"2.0","id":"aaa","result":"0x98dbb1"}']
|
||||
}
|
||||
|
||||
def "Convert error to JSON"() {
|
||||
setup:
|
||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||
call.ids[1] = 1
|
||||
def data = [
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(1)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom('{"jsonrpc": "2.0", "id": 1, "error": {"code": -32001, "message": "oops"}}', 'UTF-8'))
|
||||
.build()
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data)
|
||||
.transform(writer.toJsons(call))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == ['{"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":"oops"}}']
|
||||
}
|
||||
|
||||
def "Convert gRPC error to JSON"() {
|
||||
setup:
|
||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||
call.ids[1] = 1
|
||||
def data = [
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(1)
|
||||
.setSucceed(false)
|
||||
.setErrorMessage("Internal Error")
|
||||
.build()
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data)
|
||||
.transform(writer.toJsons(call))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == ['{"jsonrpc":"2.0","id":1,"error":{"code":-32002,"message":"Internal Error"}}']
|
||||
}
|
||||
|
||||
def "Convert few items to JSON"() {
|
||||
setup:
|
||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||
call.ids[1] = 10
|
||||
call.ids[2] = 11
|
||||
call.ids[3] = 15
|
||||
def data = [
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(1)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom('{"jsonrpc": "2.0", "id": 1, "result": "0x98dbb1"}', 'UTF-8'))
|
||||
.build(),
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(2)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom('{"jsonrpc": "2.0", "id": 2, "error": {"code": -32001, "message": "oops"}}', 'UTF-8'))
|
||||
.build(),
|
||||
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(3)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom('{"jsonrpc": "2.0", "id": 3, "result": {"hash": "0x2484f459dc"}}', 'UTF-8'))
|
||||
.build(),
|
||||
]
|
||||
when:
|
||||
def act = Flux.fromIterable(data)
|
||||
.transform(writer.toJsons(call))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act == [
|
||||
'{"jsonrpc":"2.0","id":10,"result":"0x98dbb1"}',
|
||||
'{"jsonrpc":"2.0","id":11,"error":{"code":-32001,"message":"oops"}}',
|
||||
'{"jsonrpc":"2.0","id":15,"result":{"hash":"0x2484f459dc"}}'
|
||||
]
|
||||
}
|
||||
}
|
||||
5
src/test/resources/dshackle-proxy-basic.yaml
Normal file
5
src/test/resources/dshackle-proxy-basic.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
proxy:
|
||||
port: 8080
|
||||
routes:
|
||||
- id: ethereum
|
||||
blockchain: ethereum
|
||||
9
src/test/resources/dshackle-proxy-max.yaml
Normal file
9
src/test/resources/dshackle-proxy-max.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
proxy:
|
||||
enabled: true
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
routes:
|
||||
- id: ethereum
|
||||
blockchain: ethereum
|
||||
- id: classic
|
||||
blockchain: etc
|
||||
7
src/test/resources/dshackle-proxy-two.yaml
Normal file
7
src/test/resources/dshackle-proxy-two.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
proxy:
|
||||
port: 8080
|
||||
routes:
|
||||
- id: ethereum
|
||||
blockchain: ethereum
|
||||
- id: classic
|
||||
blockchain: etc
|
||||
Reference in New Issue
Block a user