problem: separate tests are not usable in the current state, better to work on improving unit tests

(cherry picked from commit 96264b839f)
This commit is contained in:
a10zn8
2022-09-23 05:23:41 +04:00
parent 9edb703991
commit d4100d8f58
43 changed files with 2 additions and 8292 deletions

View File

@@ -49,7 +49,7 @@ class EcdsaSignerSpec extends Specification {
setup:
def conf = new SignatureConfig()
conf.enabled = true
conf.privateKey = "testing/dshackle/test_key"
conf.privateKey = "src/test/resources/signer/test_key"
def signer = new ResponseSignerFactory(conf).getObject() as EcdsaSigner
// To verify the test, check the hash of test key above:
@@ -114,7 +114,7 @@ class EcdsaSignerSpec extends Specification {
def conf = new SignatureConfig()
conf.enabled = true
conf.privateKey = "testing/dshackle/test_key"
conf.privateKey = "src/test/resources/signer/test_key"
def factory = new ResponseSignerFactory(conf)
def sk = factory.readKey(conf.algorithm, conf.privateKey).first

3
testing/.gitignore vendored
View File

@@ -1,3 +0,0 @@
gradlew
gradlew.bat
gradle/

View File

@@ -1,104 +0,0 @@
= Dshackle Integration Testing
Test for a correct work using a simulation of different upstream behaviours and checking the Dshackle against it.
.Modules:
- `dshackle` - Dshackle config used for testing environment
- `simple-upstream` - upstream simulator with predefines responses
- `trial` - actual tests
== How to run
NOTE: Run commands in project's root dir
NOTE: You need to open multiple consoles, since each command runs on foreground
== Basic Test
Makes some basic checks with a mocked upstreams
=== Run upstream emulator
.Runs two instances of RPC server
[source,bash]
----
DSHACKLE_TESTUP_PORT=18545 ./gradlew -p testing/simple-upstream run
DSHACKLE_TESTUP_PORT=18546 ./gradlew -p testing/simple-upstream run
----
=== Run Dshackle
[source,bash]
----
./gradlew run --args="--configPath=./testing/dshackle/dshackle-basic.yaml"
----
=== Run tests
[source,bash]
----
./testing/trial/gradlew -p testing/trial -PdshackleTrialMode=basic cleanTest test
----
== Real (Mainnet) Test
=== Run Dshackle
First you have to provide configuration to an upstream connected to the mainnet.
It may be a local Geth instance, or an other provider, like Infura (note that the test config doesn't support TLS or other auth)
[source,bash]
----
export DSHACKLE_TEST_ETH1_RPC=
export DSHACKLE_TEST_ETH1_WS=
export DSHACKLE_TEST_ETH1_WSORIGIN=
----
For a local Geth it would be:
[source,bash]
----
export DSHACKLE_TEST_ETH1_RPC=http://127.0.0.1:8545
export DSHACKLE_TEST_ETH1_WS=ws://127.0.0.1:8546
export DSHACKLE_TEST_ETH1_WSORIGIN=http://127.0.0.1:8546
----
Finally, run the Dshackle instance:
[source,bash]
----
./gradlew run --args="--configPath=./testing/dshackle/dshackle-real.yaml"
----
=== Run tests
[source,bash]
----
./testing/trial/gradlew -p testing/trial -PdshackleTrialMode=real cleanTest test
----
== Real (Rinkeby) Test
Runs requests against a Rinkeby Testnet.
Configuration is similar to the Mainnet config, but instead of `ETH1` is has `RINKEBY`.
[source,bash]
----
export DSHACKLE_TEST_RINKEBY_RPC=
export DSHACKLE_TEST_RINKEBY_WS=
export DSHACKLE_TEST_RINKEBY_WSORIGIN=
----
And run the Dshackle instance:
[source,bash]
----
./gradlew run --args="--configPath=./testing/dshackle/dshackle-rinkeby.yaml"
----
=== Run tests
[source,bash]
----
./testing/trial/gradlew -p testing/trial -PdshackleTrialMode=rinkeby cleanTest test
----

View File

@@ -1,14 +0,0 @@
= Testing Server
.Run from project root directory:
[source,bash]
----
./gradlew run --args="--configPath=./testing/dshackle/dshackle-mock.yaml"
----
or
[source,bash]
----
./gradlew run --args="--configPath=./testing/dshackle/dshackle-real.yaml"
----

View File

@@ -1,50 +0,0 @@
version: v1
port: 12448
tls:
enabled: false
cluster:
upstreams:
- id: test-1
node-id: 1
chain: ethereum
methods:
enabled:
- name: debug_traceTransaction
- name: test_foo
options:
disable-validation: true
connection:
ethereum-pos:
execution:
rpc:
url: "http://localhost:18545"
- id: test-2
node-id: 2
chain: ethereum
options:
disable-validation: true
connection:
execution:
ethereum-pos:
rpc:
url: "http://localhost:18546"
cache:
redis:
enabled: false
signed-response:
enabled: true
algorithm: SECP256K1
private-key: "./test_key"
proxy:
port: 18080
tls:
enabled: false
routes:
- id: eth
blockchain: ethereum

View File

@@ -1,30 +0,0 @@
version: v1
port: 12448
tls:
enabled: false
cluster:
upstreams:
- id: eth-1
chain: ethereum
connection:
ethereum:
rpc:
url: "${DSHACKLE_TEST_ETH1_RPC}"
ws:
url: "${DSHACKLE_TEST_ETH1_WS}"
origin: "${DSHACKLE_TEST_ETH1_WSORIGIN}"
cache:
redis:
enabled: false
proxy:
port: 18081
preserve-batch-order: true
tls:
enabled: false
routes:
- id: eth
blockchain: ethereum

View File

@@ -1,26 +0,0 @@
version: v1
port: 12448
tls:
enabled: false
cluster:
upstreams:
- id: rinkeby-1
chain: rinkeby
connection:
ethereum:
rpc:
url: "${DSHACKLE_TEST_RINKEBY_RPC}"
cache:
redis:
enabled: false
proxy:
port: 18081
tls:
enabled: false
routes:
- id: rinkeby
blockchain: rinkeby

View File

@@ -1,6 +0,0 @@
= Simple Testing Upstream
.Run from current directory
----
./gradlew run
----

View File

@@ -1,22 +0,0 @@
plugins {
id 'java'
id 'groovy'
id 'idea'
id 'application'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation "com.sparkjava:spark-core:2.9.1"
implementation "org.codehaus.groovy:groovy:3.0.4"
implementation "com.fasterxml.jackson.core:jackson-core:2.9.8"
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"
}
application {
mainClassName = 'testing.SimpleUpstream'
}

View File

@@ -1 +0,0 @@
rootProject.name = 'dshackle-testing-simple-upstream'

View File

@@ -1,32 +0,0 @@
package testing
import com.fasterxml.jackson.databind.ObjectMapper
class BlocksHandler implements CallHandler {
ObjectMapper objectMapper
ResourceResponse resourceResponse
BlocksHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper
this.resourceResponse = new ResourceResponse(objectMapper)
}
@Override
Result handle(String method, List<Object> params) {
if (method == "eth_blockNumber") {
return Result.ok("0x100001")
}
if (method == "eth_getBlockByNumber") {
String blockId = params[0]
println("get block $blockId")
return resourceResponse.respondWith("block-${blockId}.json")
}
if (method == "eth_getTransactionByHash") {
String txId = params[0]
return resourceResponse.respondWith("tx-${txId}.json")
}
return null
}
}

View File

@@ -1,48 +0,0 @@
package testing
interface CallHandler {
Result handle(String method, List<Object> params)
static class Result {
private Object result
private Integer errorCode
private String errorMessage
private Object errorDetails
Result(Object result, Integer errorCode, String errorMessage, Object errorDetails) {
this.result = result
this.errorCode = errorCode
this.errorMessage = errorMessage
this.errorDetails = errorDetails
}
static Result ok(Object result) {
return new Result(result, null, null, null)
}
static Result error(int errorCode, String errorMessage, Object details = null) {
return new Result(null, errorCode, errorMessage, details)
}
boolean isResult() {
return errorCode == null
}
Object getResult() {
return result
}
int getErrorCode() {
return errorCode
}
String getErrorMessage() {
return errorMessage
}
Object getErrorDetails() {
return errorDetails
}
}
}

View File

@@ -1,25 +0,0 @@
package testing
import com.fasterxml.jackson.databind.ObjectMapper
class CommonHandlers implements CallHandler {
ObjectMapper objectMapper
ResourceResponse resourceResponse
CommonHandlers(ObjectMapper objectMapper) {
this.objectMapper = objectMapper
this.resourceResponse = new ResourceResponse(objectMapper)
}
@Override
Result handle(String method, List<Object> params) {
if (method == "eth_syncing") {
return Result.ok(false)
}
if (method == "eth_chainId") {
return resourceResponse.respondWith("chain-id.json")
}
return null
}
}

View File

@@ -1,35 +0,0 @@
package testing
import java.time.Instant
import java.util.concurrent.atomic.AtomicInteger
class InternalHandler implements CallHandler {
private AtomicInteger ids = new AtomicInteger()
private List<Item> requests = new ArrayList()
def record(String json) {
requests << new Item(ids.getAndIncrement(), json)
}
@Override
Result handle(String method, List<Object> params) {
if (method == "eth_call"
&& params[0].to?.toLowerCase() == "0x0123456789abcdef0123456789abcdef00000002".toLowerCase()) {
return Result.ok(Collections.unmodifiableList(requests))
}
return null
}
class Item {
Integer id
String timestamp
String json
Item(Integer id, String json) {
this.id = id
this.timestamp = Instant.now().toString()
this.json = json
}
}
}

View File

@@ -1,8 +0,0 @@
package testing
class InvalidCallHandler implements CallHandler {
@Override
Result handle(String method, List<Object> params) {
return Result.error(-32000, "Call is not supported")
}
}

View File

@@ -1,12 +0,0 @@
package testing
class PingPongHandler implements CallHandler {
@Override
Result handle(String method, List<Object> params) {
if (method == "eth_call"
&& params[0].to?.toLowerCase() == "0x0123456789abcdef0123456789abcdef00000001".toLowerCase()) {
return Result.ok([data: params[0].data])
}
return null
}
}

View File

@@ -1,25 +0,0 @@
package testing
import com.fasterxml.jackson.databind.ObjectMapper
class ResourceResponse {
ObjectMapper objectMapper
ResourceResponse(ObjectMapper objectMapper) {
this.objectMapper = objectMapper
}
Object getResource(String name) {
String json = BlocksHandler.class.getResourceAsStream("/" + name)?.text
if (json == null) {
return null
}
return objectMapper.readValue(json, Map)
}
CallHandler.Result respondWith(String name) {
return CallHandler.Result.ok(getResource(name))
}
}

View File

@@ -1,73 +0,0 @@
package testing
import com.fasterxml.jackson.databind.ObjectMapper
import spark.Spark
class SimpleUpstream {
private int port = System.getenv("DSHACKLE_TESTUP_PORT")?.toInteger() ?: 18545
private ObjectMapper objectMapper
private List<CallHandler> handlers = []
private InternalHandler internalHandler
void prepare() {
objectMapper = new ObjectMapper()
internalHandler = new InternalHandler()
handlers << new TestcaseHandler(objectMapper)
handlers << new CommonHandlers(objectMapper)
handlers << new BlocksHandler(objectMapper)
handlers << new PingPongHandler()
handlers << internalHandler
handlers << new InvalidCallHandler()
}
void start() {
println("Starting upstream on 0.0.0.0:$port")
Spark.port(port)
Spark.post("/") { req, resp ->
try {
def requestBody = req.body()
println("request: $requestBody")
internalHandler.record(requestBody)
Map json = objectMapper.readValue(requestBody, Map)
def id = json["id"]
String method = json["method"].toString()
List<Object> params = json.containsKey("params") ? json["params"] as List<Object> : []
CallHandler.Result result = handlers.findResult { h ->
return h.handle(method, params)
}
Map resultJson = [
id : id,
jsonrpc: "2.0"
]
if (result.isResult()) {
resultJson["result"] = result.result
} else {
resultJson["error"] = [
code : result.getErrorCode(),
message: result.getErrorMessage()
]
if (result.getErrorDetails() != null) {
resultJson["error"]["data"] = result.getErrorDetails()
}
}
resp.status(200)
resp.header("content-type", "application/json")
return objectMapper.writeValueAsString(resultJson)
} catch (Throwable t) {
t.printStackTrace()
}
}
}
public static void main(String[] args) {
def server = new SimpleUpstream()
server.prepare()
server.start()
}
}

View File

@@ -1,44 +0,0 @@
package testing
import com.fasterxml.jackson.databind.ObjectMapper
class TestcaseHandler implements CallHandler {
ObjectMapper objectMapper
ResourceResponse resourceResponse
TestcaseHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper
this.resourceResponse = new ResourceResponse(objectMapper)
}
@Override
Result handle(String method, List<Object> params) {
// https://github.com/emeraldpay/dshackle/issues/35
if (method == "eth_call"
&& params[0].to?.toLowerCase() == "0x542156d51D10Db5acCB99f9Db7e7C91B74E80a2c".toLowerCase()) {
return Result.error(-32015, "VM execution error.")
}
// https://github.com/emeraldpay/dshackle/issues/35 (second)
if (method == "eth_call"
&& params[0].to?.toLowerCase() == "0x8ee2a5aca4f88cb8c757b8593d0734855dcc0eba".toLowerCase()) {
return Result.error(-32015, "VM execution error.", "revert: SafeMath: division by zero")
}
// https://github.com/emeraldpay/dshackle/issues/43
if (method == "debug_traceTransaction"
&& params[0].toLowerCase() == "0xd949bc0fe1a5d16f4522bc47933554dcc4ada0493ff71ee1973b2410257af9fe".toLowerCase()) {
return resourceResponse.respondWith("trace-0xd949bc.json")
}
// https://github.com/emeraldpay/dshackle/issues/67
if (method == "eth_call"
&& params[0].to?.toLowerCase() == "0xdAC17F958D2ee523a2206206994597C13D831ec7".toLowerCase()) {
return Result.error(-32000, "invalid opcode: opcode 0xfe not defined")
}
// https://github.com/emeraldpay/dshackle/issues/67, when a custom method configured
if (method == "test_foo"
&& params[0].to?.toLowerCase() == "0xdAC17F958D2ee523a2206206994597C13D831ec7".toLowerCase()) {
return Result.error(-32000, "invalid opcode: opcode 0xfe not defined")
}
return null
}
}

View File

@@ -1,25 +0,0 @@
{
"difficulty": "0xc6d2fa46fd6",
"extraData": "0xd783010400844765746887676f312e352e31856c696e7578",
"gasLimit": "0x2fefd8",
"gasUsed": "0xa410",
"hash": "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner": "0x68795c4aa09d6f4ed3e5deddf8c2ad3049a601da",
"mixHash": "0x72224fd0b7b32d0fea13f2953eba28d2512af43632725ed75583dd30e42dfc64",
"nonce": "0xf1a453a44f9de627",
"number": "0x100000",
"parentHash": "0x2dcaf9a8a3fd329d925eb47c1f22022d4f2d562500e546d878bad4247d013858",
"receiptsRoot": "0x178000485de197ed06a4ebada431864e5b564b43f994fb8888521f74b4b3257b",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x303",
"stateRoot": "0xeaddaa0b6b673ba3f8cb7235ca656340ccf703295cf5252cb04a4202c47252c2",
"timestamp": "0x56cc7b73",
"totalDifficulty": "0x6bab93cdf810b22a",
"transactions": [
"0x01c5a8461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa",
"0xb4216e88df6ebfe666bbe43370d1ddfe8d9c1975d2b8375a522700f7f59c3ed0"
],
"transactionsRoot": "0x91d9b419b34a790cc2a7d59c6b95e57ccc5ec9bc1034ce9510dfb551d3049c93",
"uncles": []
}

View File

@@ -1,26 +0,0 @@
{
"difficulty": "0xc6ba1fe7c49",
"extraData": "0xd783010400844765746887676f312e352e31856c696e7578",
"gasLimit": "0x2fefd8",
"gasUsed": "0xa410",
"hash": "0x18c68d9ba58772a4409d65d61891b25db03a105a7769ae08ef2cff697921b446",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner": "0x0c729be7c39543c3d549282a40395299d987cec2",
"mixHash": "0xf8679fe0f04d5aad4844694cc8fb1a1b97482a5822658379c9bb8633f91daf97",
"nonce": "0x347a267c0ec88618",
"number": "0x100001",
"parentHash": "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e",
"receiptsRoot": "0x4f4ed1139baadbd4506d6b0c330335d2108715095fb93fd282bd69aa9edc09eb",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x304",
"stateRoot": "0xee13382462fcd5748a403ca131c38e871fa7206bca2b962f67b59e800741daa3",
"timestamp": "0x56cc7b8c",
"totalDifficulty": "0x6baba0399a0f2e73",
"transactions": [
"0x146b8f4b6300c73bb7476359b9f1c5ee3f686a86b2aa673552cf0f9de9a42e77",
"0xe589a39acea3091b584b650158d08b159aa07e97b8e8cddb8f81cb606e13382e"
],
"transactionsRoot": "0xc90078e2af52aef81815cb2a71c22ebd781dd658dd953d9df57f7769a0b2fe51",
"uncles": [],
"testFoo": "bar"
}

View File

@@ -1,5 +0,0 @@
{
"id": 83,
"jsonrpc": "2.0",
"result": "0x3d"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"blockHash": "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e",
"blockNumber": "0x100000",
"from": "0x68795c4aa09d6f4ed3e5deddf8c2ad3049a601da",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0x01c5a8461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa",
"input": "0x",
"nonce": "0x2f31",
"r": "0x1d263f32de00456cbc3e4099c623a6ca48e3188f01dd16461687512f8c3bb7d6",
"s": "0x62629f42a98f7c5fa60f8c3630cf67b5eb4772e0ff65a458d2116c15e22d6451",
"to": "0xdae787ec66e65c60ad35203800b97b45fcb0f909",
"transactionIndex": "0x0",
"v": "0x1b",
"value": "0x2b81097c919a9e000"
}

View File

@@ -1,16 +0,0 @@
{
"blockHash": "0x18c68d9ba58772a4409d65d61891b25db03a105a7769ae08ef2cff697921b446",
"blockNumber": "0x100001",
"from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0x146b8f4b6300c73bb7476359b9f1c5ee3f686a86b2aa673552cf0f9de9a42e77",
"input": "0x",
"nonce": "0x32c13",
"r": "0xe6ca408c2e91163b46c6bb66054a000a25b6c1d8024460499a185ab2e4bb41a0",
"s": "0x53bc43662fda46c2e7051fa704b75ec815e9102cca5400817011ffd99e77139d",
"to": "0x7388ae5bc873163025fffe5956322f734f9b570d",
"transactionIndex": "0x0",
"v": "0x1b",
"value": "0xe660f79cf5e4800"
}

View File

@@ -1,16 +0,0 @@
{
"blockHash": "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e",
"blockNumber": "0x100000",
"from": "0x68795c4aa09d6f4ed3e5deddf8c2ad3049a601da",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0xb4216e88df6ebfe666bbe43370d1ddfe8d9c1975d2b8375a522700f7f59c3ed0",
"input": "0x",
"nonce": "0x2f32",
"r": "0xc25ceef654cd3bd734201fc08589970e0663431121bc47cc4a70335e072b09c0",
"s": "0x1c67b037a59a8f5badde3a7eb586d7888dcf6dad64aabb080c4c0db593a91254",
"to": "0x04a2a3ee9c9ea82e1f918ece209f8a7e6d7521e7",
"transactionIndex": "0x1",
"v": "0x1c",
"value": "0x377fb2f2cbd73c00"
}

View File

@@ -1,16 +0,0 @@
{
"blockHash": "0x18c68d9ba58772a4409d65d61891b25db03a105a7769ae08ef2cff697921b446",
"blockNumber": "0x100001",
"from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0xe589a39acea3091b584b650158d08b159aa07e97b8e8cddb8f81cb606e13382e",
"input": "0x",
"nonce": "0x32c14",
"r": "0x77fe7cfb03258f2eed8ffbbdc19f73f4a6095a55a7e06f81ade02f3435f7e249",
"s": "0x821ad733ceecfa7567e9c8ffb66e3f8bbc3b2b7b21bacadb2a065e57b2a6992",
"to": "0x1c774e876a46f51e305add171ba3bb4589408273",
"transactionIndex": "0x1",
"v": "0x1b",
"value": "0xe5ef66c73b5fc00"
}

View File

@@ -1,8 +0,0 @@
= Actual Tests
Expects running _Simple Upstream_ and _Testing Dshackle_.
.Run from current directory
----
./gradlew check
----

View File

@@ -1,35 +0,0 @@
plugins {
id 'java'
id 'groovy'
id 'idea'
}
repositories {
mavenLocal()
mavenCentral()
maven { url "https://maven.emrld.io" }
}
dependencies {
implementation "org.apache.httpcomponents:httpclient:4.5.12"
implementation "org.codehaus.groovy:groovy:3.0.4"
implementation "com.fasterxml.jackson.core:jackson-core:2.9.8"
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"
implementation "io.grpc:grpc-netty:1.46.0"
implementation "org.bouncycastle:bcprov-jdk15on:1.61"
implementation("io.emeraldpay:emerald-api:0.12-alpha.1") {
exclude group: 'com.salesforce.servicelibs', module: 'reactor-grpc'
}
testImplementation "org.spockframework:spock-core:2.0-M3-groovy-3.0"
}
test {
systemProperty "trialMode", project.getProperty("dshackleTrialMode")
systemProperty "signatureKey", project.getProperty("signatureKey")
useJUnitPlatform()
testLogging {
events "PASSED", "FAILED"
}
}

View File

@@ -1 +0,0 @@
rootProject.name = 'dshackle-testing-trial'

View File

@@ -1,6 +0,0 @@
package io.emeraldpay.dshackle.testing.trial
interface Client {
Map<String, Object> execute(String method, List<Object> params)
Map<String, Object> execute(Object id, String method, List<Object> params)
}

View File

@@ -1,20 +0,0 @@
package io.emeraldpay.dshackle.testing.trial
class Debugger {
public static enabled = true
static void showOut(String json) {
if (!enabled) {
return
}
println(">> $json")
}
static void showIn(String json) {
if (!enabled) {
return
}
println("<< $json")
}
}

View File

@@ -1,64 +0,0 @@
package io.emeraldpay.dshackle.testing.trial
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.primitives.Bytes
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
import io.emeraldpay.grpc.Chain
import io.grpc.ManagedChannel
import io.grpc.netty.NettyChannelBuilder
class ProtoClient implements Client {
private int sequence = 0;
private ObjectMapper objectMapper;
private ReactorBlockchainGrpc.ReactorBlockchainStub stub
private Chain chain
ProtoClient(ManagedChannel channel, Chain chain) {
this.stub = ReactorBlockchainGrpc.newReactorStub(channel)
this.objectMapper = new ObjectMapper()
this.chain= chain
}
static ProtoClient create(String host, int port, Chain chain) {
def channel = NettyChannelBuilder.forAddress(host, port)
.maxInboundMessageSize(Integer.MAX_VALUE)
.usePlaintext()
new ProtoClient(channel.build(), chain)
}
static ProtoClient basic() {
create("localhost", 12448, Chain.ETHEREUM)
}
BlockchainOuterClass.NativeCallReplyItem executeNative(String method, List<Object> params, Long nonce) {
def req = BlockchainOuterClass.NativeCallRequest
.newBuilder()
.setChain(Common.ChainRef.CHAIN_ETHEREUM)
.addItems(BlockchainOuterClass.NativeCallItem
.newBuilder()
.setId(0)
.setNonce(nonce)
.setMethod(method)
.setPayload(ByteString.copyFrom(objectMapper.writeValueAsBytes(params)))
.build()
).build()
stub.nativeCall(req)
.single()
.block()
}
Map<String, Object> execute(String method, List<Object> params) {
return execute(sequence++, method, params)
}
Map<String, Object> execute(Object id, String method, List<Object> params) {
def result = executeNative(method, params, 0L)
if (result.errorMessage != "") {
return [error: result.errorMessage]
} else {
return objectMapper.readerFor(Map).readValue(Bytes.concat("{\"result\": ".bytes, result.payload.toByteArray(), "}".bytes))
}
}
}

View File

@@ -1,83 +0,0 @@
package io.emeraldpay.dshackle.testing.trial
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex
import org.apache.http.HttpHeaders
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import org.apache.http.impl.client.HttpClients
class ProxyClient implements Client {
private int sequence = 0;
private String url
private ObjectMapper objectMapper
ProxyClient(String url) {
this.url = url
this.objectMapper = new ObjectMapper()
}
static ProxyClient ethereumMock() {
return forPrefix(18080, "eth")
}
static ProxyClient ethereumReal() {
return forPrefix(18081, "eth")
}
static ProxyClient ethereumRinkeby() {
return forPrefix(18081, "rinkeby")
}
static ProxyClient forPrefix(String prefix) {
return forPrefix(18080, prefix)
}
static ProxyClient forPrefix(int port, String prefix) {
return new ProxyClient("http://127.0.0.1:$port/$prefix")
}
static ProxyClient forOriginal(int port) {
return new ProxyClient("http://127.0.0.1:$port")
}
Map<String, Object> execute(String method, List<Object> params) {
return execute(sequence++, method, params)
}
Map<String, Object> execute(Object id, String method, List<Object> params) {
Map jsonData = [
id : id,
jsonrpc: "2.0",
method : method,
params : params
]
String reqJson = objectMapper.writeValueAsString(jsonData)
Debugger.showOut(reqJson)
CloseableHttpClient httpClient = null
try {
httpClient = HttpClients.createDefault()
def request = new HttpPost(url)
request.setEntity(new StringEntity(reqJson, ContentType.APPLICATION_JSON))
CloseableHttpResponse response = httpClient.execute(request)
if (response.getStatusLine().statusCode != 200) {
throw new IllegalStateException("Non-ok status: " + response.getStatusLine().statusCode)
}
String respJson = response.entity.content.text
Debugger.showIn(respJson)
return objectMapper.readerFor(Map).readValue(respJson)
} finally {
httpClient?.close()
}
return [error: "NOT EXECUTED"]
}
}

View File

@@ -1,63 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.basicproxy
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
class DispatchSpec extends Specification {
def client = ProxyClient.forPrefix("eth")
def "multiple calls routed roughly equal to upstreams"() {
when:
def calls1before = ProxyClient.forOriginal(18545).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls2before = ProxyClient.forOriginal(18546).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
100.times {
client.execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000001", data: "0x00000000" + Integer.toString(it, 16)]])
}
def calls1after = ProxyClient.forOriginal(18545).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls2after = ProxyClient.forOriginal(18546).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls1 = onlyNew(calls1before, calls1after)
def calls2 = onlyNew(calls2before, calls2after)
then:
calls1.size() >= 48
calls2.size() >= 48
calls1.size() + calls2.size() == 100
}
def "multiple calls routed roughly equal to upstreams - for latest block"() {
when:
def calls1before = ProxyClient.forOriginal(18545).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls2before = ProxyClient.forOriginal(18546).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
100.times {
client.execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000001", data: "0x00000000" + Integer.toString(it, 16)], "0x100001"])
}
def calls1after = ProxyClient.forOriginal(18545).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls2after = ProxyClient.forOriginal(18546).execute("eth_call", [[to: "0x0123456789abcdef0123456789abcdef00000002"]]).result as List
def calls1 = onlyNew(calls1before, calls1after)
def calls2 = onlyNew(calls2before, calls2after)
then:
calls1.size() >= 48
calls2.size() >= 48
calls1.size() + calls2.size() == 100
}
private List onlyNew(List before, List after) {
return after.findAll { a ->
!before.any { b ->
b.id == a.id
}
}.findAll {
(it.json as String).contains("0x0123456789abcdef0123456789abcdef00000001")
}
}
}

View File

@@ -1,74 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.basicproxy
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
class GivesErrorSpec extends Specification {
def client = ProxyClient.forPrefix("eth")
def "Gives error message"() {
// issue #42
when:
def act = client.execute("eth_call", [[to: "0x542156d51D10Db5acCB99f9Db7e7C91B74E80a2c"]])
then:
act.result == null
act.error != null
with(act.error) {
code == -32015
message == "VM execution error."
}
}
def "Gives error data"() {
// issue #42
when:
def act = client.execute("eth_call", [[to: "0x8ee2a5aca4f88cb8c757b8593d0734855dcc0eba"]])
then:
act.result == null
act.error != null
with(act.error) {
code == -32015
message == "VM execution error."
data == "revert: SafeMath: division by zero"
}
}
def "Dispatch error from upstream when block is specified"() {
// issue #67
when:
def call = [
to : "0xdAC17F958D2ee523a2206206994597C13D831ec7",
from: "0xEF65ffB384c99a00403EAa22115323a555700D79",
data: "0xa9059cbb0000000000000000000000003f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be000000000000000000000000000000000000000000000000000000004856fb60"
]
def act = client.execute("eth_call", [call, "0x100000"])
then:
act.result == null
act.error != null
with(act.error) {
code == -32000
message == "invalid opcode: opcode 0xfe not defined"
}
}
def "Dispatch error from upstream when custome method is used"() {
// issue #67
when:
def call = [
to : "0xdAC17F958D2ee523a2206206994597C13D831ec7",
from: "0xEF65ffB384c99a00403EAa22115323a555700D79",
data: "0xa9059cbb0000000000000000000000003f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be000000000000000000000000000000000000000000000000000000004856fb60"
]
def act = client.execute("test_foo", [call, "0x100000"])
then:
act.result == null
act.error != null
with(act.error) {
code == -32000
message == "invalid opcode: opcode 0xfe not defined"
}
}
}

View File

@@ -1,36 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.basicproxy
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
class HardcodedCallsSpec extends Specification {
def client = ProxyClient.forPrefix("eth")
def "get syncing"() {
when:
def act = client.execute("eth_syncing", [])
then:
act.result == false
act.error == null
}
def "get network version"() {
when:
def act = client.execute("net_version", [])
then:
act.result == "1"
act.error == null
}
def "get peer count"() {
when:
def act = client.execute("net_peerCount", [])
then:
act.result == "0x2a"
act.error == null
}
}

View File

@@ -1,20 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.basicproxy
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
class MetamaskCallSpec extends Specification {
def client = ProxyClient.forPrefix("eth")
def "use long id"() {
when:
def act = client.execute(1057264140543346, "net_version", [])
then:
act.id == 1057264140543346
act.result != null
act.error == null
}
}

View File

@@ -1,166 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.basicproxy
import com.google.common.primitives.Bytes
import com.google.common.primitives.Longs
import io.emeraldpay.dshackle.testing.trial.ProtoClient
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import org.apache.commons.codec.binary.Hex
import spock.lang.IgnoreIf
import spock.lang.Shared
import spock.lang.Specification
import java.security.KeyFactory
import org.bouncycastle.util.io.pem.PemReader
import java.security.MessageDigest
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
class StandardCallsSpec extends Specification {
@Shared client_proto = ProtoClient.basic()
@Shared client_proxy = ProxyClient.forPrefix("eth")
@Shared clients = [client_proto, client_proxy]
def "get height"() {
when:
def act = client.execute("eth_blockNumber", [])
then:
act.result == "0x100001"
act.error == null
where:
client << clients
}
def "get block"() {
when:
def act = client.execute("eth_getBlockByNumber", ["0x100001", false])
then:
act.result != null
with(act.result) {
number == "0x100001"
hash == "0x18c68d9ba58772a4409d65d61891b25db03a105a7769ae08ef2cff697921b446"
transactions == [
"0x146b8f4b6300c73bb7476359b9f1c5ee3f686a86b2aa673552cf0f9de9a42e77",
"0xe589a39acea3091b584b650158d08b159aa07e97b8e8cddb8f81cb606e13382e"
]
}
act.error == null
where:
client << clients
}
def "get non-existing block"() {
when:
def act = client.execute("eth_getBlockByNumber", ["0x200001", false])
then:
act.result == null
act.error == null
where:
client << clients
}
def "get tx"() {
when:
def act = client.execute("eth_getTransactionByHash", ["0x01c5a8461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa"])
then:
act.result != null
with(act.result) {
blockHash == "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e"
}
act.error == null
where:
client << clients
}
def "get non-existing tx"() {
when:
def act = client.execute("eth_getTransactionByHash", ["0x000000461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa"])
then:
act.result == null
act.error == null
where:
client << clients
}
def "get block with txes"() {
when:
def act = client.execute("eth_getBlockByNumber", ["0x100001", true])
then:
act.result != null
with(act.result) {
number == "0x100001"
hash == "0x18c68d9ba58772a4409d65d61891b25db03a105a7769ae08ef2cff697921b446"
transactions.size() == 2
transactions[0] instanceof Map
transactions[1] instanceof Map
with(transactions.find { it.hash == "0x146b8f4b6300c73bb7476359b9f1c5ee3f686a86b2aa673552cf0f9de9a42e77" }) {
from == "0x2a65aca4d5fc5b5c859090a6c34d164135398226"
}
}
act.error == null
where:
client << clients
}
def "returns original block json"() {
when:
def act = client.execute("eth_getBlockByNumber", ["0x100001", false])
then:
act.result != null
with(act.result) {
testFoo == "bar"
}
act.error == null
where:
client << clients
}
def "returns original block json with tx"() {
when:
def act = client.execute("eth_getBlockByNumber", ["0x100001", true])
then:
act.result != null
with(act.result) {
testFoo == "bar"
}
act.error == null
where:
client << clients
}
def "check response signature with nonce"() {
when:
def act = client_proto.executeNative("eth_blockNumber", [], 10)
def keyFactory = KeyFactory.getInstance("EC")
def key = new File(System.getProperty('signatureKey'))
def reader = new PemReader(key.newReader())
def keySpec = new X509EncodedKeySpec(reader.readPemObject().getContent())
def pubKey = keyFactory.generatePublic(keySpec)
def sig = Signature.getInstance("SHA256withECDSA")
sig.initVerify(pubKey)
def sep = "/".bytes
def digest = MessageDigest.getInstance("SHA-256")
def messageHash = digest.digest(act.payload.toByteArray())
def sigmessage = Bytes.concat("DSHACKLESIG".bytes,
sep,
10L.toString().bytes,
sep,
act.signature.upstreamId.bytes,
sep,
Hex.encodeHexString(messageHash).bytes)
sig.update(sigmessage)
then:
(new String(act.payload.toByteArray())) == "\"0x100001\""
sig.verify(act.signature.signature.toByteArray())
}
def "check response signature without nonce"() {
when:
def act = client_proto.executeNative("eth_blockNumber", [], 0L)
then:
act.signature.signature.isEmpty()
}
}

View File

@@ -1,40 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.mainnet
import io.emeraldpay.dshackle.testing.trial.Debugger
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
import spock.lang.Timeout
@IgnoreIf({ System.getProperty('trialMode') != 'real' })
class GetBlockSpec extends Specification {
def client = ProxyClient.ethereumReal()
def setup() {
Debugger.enabled = false
}
def cleanup() {
Debugger.enabled = true
}
@Timeout(15)
def "Correct order for transactions on #height"() {
expect:
def results = [
(client.execute("eth_getBlockByNumber", ["0x" + Integer.toString(height, 16), false]).result["transactions"] as List<String>),
(client.execute("eth_getBlockByNumber", ["0x" + Integer.toString(height, 16), true]).result["transactions"] as List<Map<String, Object>>)
.collect { it.hash }
]
println(results[0])
println(results[1])
results[0] == results[1]
where:
height << (10_000_000..10_000_500)
}
}

View File

@@ -1,30 +0,0 @@
package io.emeraldpay.dshackle.testing.trial.rinkeby
import io.emeraldpay.dshackle.testing.trial.ProxyClient
import spock.lang.IgnoreIf
import spock.lang.Specification
@IgnoreIf({ System.getProperty('trialMode') != 'rinkeby' })
class CallContractSpec extends Specification {
def client = ProxyClient.ethereumRinkeby()
def "get block"() {
when:
def act = client.execute(65, "eth_call",
[
[
"to" : "0xB8Ba177465b3d696180742c6ba58C408175CB6Dd",
"data": "0x70a08231000000000000000000000000da3f87aa38d07f1fc836873a3b34ec23088ef78a"
],
"0xc90f1c8c125a4d5b90742f16947bdb1d10516f173fd7fc51223d10499de2a812"
]
)
then:
act.id == 65
act.result != null
act.result == "0x000000000000000000000000000000000000000000000000000000003af9a800"
act.error == null
}
}