From 6c96d54cfadc422242097b669c3e3ccab0d8840e Mon Sep 17 00:00:00 2001 From: Maxksim Fomenkov Date: Tue, 23 Aug 2022 18:59:39 +0300 Subject: [PATCH 1/4] add Ethereum block validation for HEAD subscription --- .../dshackle/upstream/AbstractHead.kt | 27 +++-- .../dshackle/upstream/BlockValidator.kt | 16 +++ .../upstream/ethereum/DefaultEthereumHead.kt | 3 +- .../ethereum/EthereumBlockValidator.kt | 102 ++++++++++++++++++ .../dshackle/upstream/ethereum/RLP.kt | 102 ++++++++++++++++++ .../dshackle/upstream/AbstractHeadSpec.groovy | 2 +- .../EthereumBlockValidatorSpec.groovy | 29 +++++ .../dshackle/upstream/ethereum/RLPSpec.groovy | 67 ++++++++++++ .../resources/blocks/eth_valid_block_1.json | 18 ++++ .../resources/blocks/eth_valid_block_2.json | 22 ++++ 10 files changed, 376 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/RLPSpec.groovy create mode 100644 src/test/resources/blocks/eth_valid_block_1.json create mode 100644 src/test/resources/blocks/eth_valid_block_2.json diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt index 48ebfe63..6dd43d35 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -16,6 +16,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.ethereum.EthereumBlockValidator import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import org.slf4j.LoggerFactory import reactor.core.Disposable @@ -25,7 +26,8 @@ import reactor.core.scheduler.Schedulers import reactor.kotlin.core.publisher.toMono abstract class AbstractHead( - private val forkChoice: ForkChoice + private val forkChoice: ForkChoice, + private val blockValidator: BlockValidator = BlockValidator.ALWAYS_VALID ) : Head { companion object { @@ -56,17 +58,22 @@ abstract class AbstractHead( } .subscribeOn(Schedulers.boundedElastic()) .subscribe { block -> - notifyBeforeBlock() - when (val choiceResult = forkChoice.choose(block)) { - is ForkChoice.ChoiceResult.Updated -> { - val newHead = choiceResult.nwhead - log.debug("New block ${newHead.height} ${newHead.hash}") - val result = stream.tryEmitNext(newHead) - if (result.isFailure && result != Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER) { - log.warn("Failed to dispatch block: $result as ${this.javaClass}") + if (blockValidator.isValid(block)) { + notifyBeforeBlock() + when (val choiceResult = forkChoice.choose(block)) { + is ForkChoice.ChoiceResult.Updated -> { + val newHead = choiceResult.nwhead + log.debug("New block ${newHead.height} ${newHead.hash}") + val result = stream.tryEmitNext(newHead) + if (result.isFailure && result != Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER) { + log.warn("Failed to dispatch block: $result as ${this.javaClass}") + } } + + is ForkChoice.ChoiceResult.Same -> {} } - is ForkChoice.ChoiceResult.Same -> {} + } else { + log.warn("Invalid block $block}") } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt new file mode 100644 index 00000000..3e7b61b7 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt @@ -0,0 +1,16 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.data.BlockContainer + +interface BlockValidator { + + fun isValid(block: BlockContainer): Boolean + + class AlwaysValid : BlockValidator { + override fun isValid(block: BlockContainer): Boolean = true + } + + companion object { + val ALWAYS_VALID = AlwaysValid() + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt index ded52346..1432f994 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt @@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.AbstractHead +import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest @@ -29,7 +30,7 @@ import reactor.core.publisher.Mono open class DefaultEthereumHead( forkChoice: ForkChoice -) : Head, AbstractHead(forkChoice) { +) : Head, AbstractHead(forkChoice, EthereumBlockValidator()) { companion object { private val log = LoggerFactory.getLogger(DefaultEthereumHead::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt new file mode 100644 index 00000000..03374716 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt @@ -0,0 +1,102 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import com.fasterxml.jackson.databind.JsonNode +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.upstream.BlockValidator +import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.encode +import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.encodeBigInt +import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.encodeList +import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexString +import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexStringI +import org.bouncycastle.jcajce.provider.digest.Keccak +import org.slf4j.LoggerFactory +import java.math.BigInteger + +class EthereumBlockValidator : BlockValidator { + + companion object { + val MAX_BIG_INT: BigInteger = BigInteger.valueOf(2).pow(256) + val NUMBER_ELEMENTS = setOf("difficulty", "number", "gasLimit", "gasUsed", "timestamp", "baseFeePerGas") + val ELEMENTS = setOf( + "parentHash", + "sha3Uncles", + "miner", + "stateRoot", + "transactionsRoot", + "receiptsRoot", + "logsBloom", + "difficulty", + "number", + "gasLimit", + "gasUsed", + "timestamp", + "extraData", + "mixHash", + "nonce", + "baseFeePerGas" + ) + + private val log = LoggerFactory.getLogger(EthereumBlockValidator::class.java) + } + + override fun isValid(block: BlockContainer): Boolean = + block.json?.let { + println(String(it)) + val node = Global.objectMapper.readTree(it) + val rlpEncoded = rlp(node) + val hashValid = validateHash(block.hash, rlpEncoded) + + if (!hashValid) { + log.warn("Hash not valid for block ${block.hash}") + } + val difficultyValid = validateDifficulty(node, rlpEncoded) + if (!difficultyValid) { + log.warn("PoW not valid for block ${block.hash}") + } + + hashValid && difficultyValid + } ?: false + + private fun rlp(node: JsonNode): Map = + ELEMENTS.mapNotNull { + node.get(it)?.asText()?.let { text -> + it to text + } + }.associate { + it.first to if (NUMBER_ELEMENTS.contains(it.first)) encodeBigInt(fromHexStringI(it.second)) else encode( + fromHexString(it.second) + ) + } + + private fun validateHash(blockHash: BlockId, rlpEncoded: Map): Boolean { + val elements = ELEMENTS.mapNotNull { rlpEncoded[it] }.toList() + val encoded = encodeList(elements) + return blockHash.value.contentEquals(sha3(encoded)) + } + + private fun validateDifficulty(node: JsonNode, rlpEncoded: Map): Boolean { + val nonce = fromHexStringI(node["nonce"].asText()) + val difficulty = fromHexStringI(node["difficulty"].asText()) + val mix = fromHexString(node["mixHash"].asText()) + + val target = MAX_BIG_INT.divide(difficulty) + val encoded = encodeList( + ELEMENTS.filterNot { it in listOf("nonce", "mixHash") } + .mapNotNull { rlpEncoded[it] } + .toList() + ) + val sha3 = sha3(encoded) + val seed = Keccak.Digest512().digest(sha3.plus(nonce.asUint64())) + val result = sha3(seed.plus(mix)) + return target >= BigInteger(1, result) + } + + private fun BigInteger.asUint64() = this.toByteArray().let { bytes -> + ByteArray(8) { bytes[bytes.size - it - 1] } + } + + private fun sha3(byteArray: ByteArray): ByteArray = + Keccak.Digest256().digest(byteArray) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt new file mode 100644 index 00000000..05c76c3f --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt @@ -0,0 +1,102 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import java.math.BigInteger + +class RLP { + companion object { + private const val SIZE_THRESHOLD = 56 + private const val OFFSET_SHORT_ITEM = 0x80 + private const val OFFSET_LONG_ITEM = 0xb7 + private const val OFFSET_SHORT_LIST = 0xc0 + private const val OFFSET_LONG_LIST = 0xf7 + + @JvmStatic + fun toHexString(bytes: ByteArray) = bytes.toHexString() + + @JvmStatic + fun fromHexString(hexString: String?): ByteArray = + hexString?.let { hex -> + hex.replace("0x", "") + .takeIf { it.length % 2 == 0 } + ?.chunked(2) + ?.map { it.toInt(16).toByte() } + ?.toByteArray() + ?: throw IllegalArgumentException("Invalid HEX string '$hexString'") + } ?: byteArrayOf() + + @JvmStatic + fun fromHexStringI(hexString: String?): BigInteger = + hexString?.let { hex -> + val replace = hex.replace("0x", "") + BigInteger(replace, 16) + } ?: BigInteger.ZERO + + @JvmStatic + fun encodeBigInt(value: BigInteger) = + encode(value.asUnsignedByteArray()) + + @JvmStatic + fun encodeString(str: String) = + encode(str.toByteArray()) + + @JvmStatic + fun encode(srcData: ByteArray?): ByteArray { + if (srcData == null || srcData.isEmpty()) { + return byteArrayOf(OFFSET_SHORT_ITEM.toByte()) + } + if (srcData.size == 1 && (srcData[0].toInt() and 0xFF < OFFSET_SHORT_ITEM)) { + return srcData + } + if (srcData.size < SIZE_THRESHOLD) { + val length = OFFSET_SHORT_ITEM + srcData.size + return byteArrayOf(length.toByte()).plus(srcData) + } + val len = toMinimalByteArray(srcData.size) + return byteArrayOf((OFFSET_LONG_ITEM + len.size).toByte()).plus(len).plus(srcData) + } + + @JvmStatic + fun encodeStringList(vararg elements: String) = + encodeList(elements.map { it.toByteArray() }.map { encode(it) }) + + @JvmStatic + fun encodeList(elements: List): ByteArray { + val totalLength = elements.sumOf { it.size } + if (totalLength < SIZE_THRESHOLD) { + return byteArrayOf((OFFSET_SHORT_LIST + totalLength).toByte()).plus( + elements.reduce { a, b -> a.plus(b) } + ) + } + + val len = toMinimalByteArray(totalLength) + return byteArrayOf((OFFSET_LONG_LIST + len.size).toByte()) + .plus(len) + .plus(elements.reduce { a, b -> a.plus(b) }) + } + + private fun toMinimalByteArray(value: Int): ByteArray { + val encoded = toByteArray(value) + for (i in encoded.indices) { + if (encoded[i].toInt() != 0) { + return encoded.copyOfRange(i, encoded.size) + } + } + return byteArrayOf() + } + + private fun toByteArray(value: Int): ByteArray { + return byteArrayOf( + (value shr 24 and 0xff).toByte(), + (value shr 16 and 0xff).toByte(), + (value shr 8 and 0xff).toByte(), + (value and 0xff).toByte() + ) + } + } +} + +fun ByteArray.toHexString() = joinToString(separator = "") { "%02x".format(it) }.uppercase() +fun BigInteger.asUnsignedByteArray(): ByteArray = + toByteArray().let { + if (it[0] == 0.toByte()) it.copyOfRange(1, it.size) else it + } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy index 48bc614b..028c9150 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy @@ -132,7 +132,7 @@ class AbstractHeadSpec extends Specification { BlockContainer getHead() { return null } - }) + }, new BlockValidator.AlwaysValid()) } } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy new file mode 100644 index 00000000..dc6cf739 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy @@ -0,0 +1,29 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import com.fasterxml.jackson.databind.JsonNode +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import org.apache.commons.io.FileUtils +import spock.lang.Specification + +import java.time.Instant + +class EthereumBlockValidatorSpec extends Specification { + + def validator = new EthereumBlockValidator() + + def "test validate block"() { + expect: + def path = "src/test/resources/blocks/${file}.json" + def bytes = FileUtils.readFileToByteArray(new File(path)) + + def container = new BlockContainer(1L, BlockId.from(hash), BigInteger.ONE, Instant.now(), false, bytes, null, Collections.emptyList(), 1) + validator.isValid(container) == expect + + where: + file || expect || hash + "eth_valid_block_1" || true || "0xae174c5cf816f820b17ae13d120fff057406ce12f448422f0d522459d4ae646b" + "eth_valid_block_2" || true || "0x95fd7b85ed04cc67884d7c33d13df3ebb568a2c1532021316c296038bda71776" + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/RLPSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/RLPSpec.groovy new file mode 100644 index 00000000..a44ff0b8 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/RLPSpec.groovy @@ -0,0 +1,67 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import spock.lang.Specification + +import static io.emeraldpay.dshackle.upstream.ethereum.RLP.toHexString + +class RLPSpec extends Specification { + + def "encode bytes"() { + expect: + toHexString(RLP.encode(input)) == output + + where: + input || output + new byte[0] || "80" + new byte[]{0} || "00" + new byte[]{1} || "01" + new byte[]{0x7F} || "7F" + new byte[]{0x80} || "8180" + new byte[]{0xFF} || "81FF" + new byte[]{1, 2, 3} || "83010203" + longByteArray() || "B8390102030000000000000000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000" + } + + def "encode string"() { + expect: + toHexString(RLP.encodeString(input)) == output + + where: + input || output + "dog" || "83646F67" + "Lorem ipsum dolor sit amet, consectetur adipisicing eli" || "B74C6F72656D20697073756D20646F6C6F72207369742" + + "0616D65742C20636F6E7365637465747572206164697069736963696E6720656C69" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat" || "B904004C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E73656374657475722061646970697363696E6720656C69742E20437572616269747572206D6175726973206D61676E612C20737573636970697420736564207665686963756C61206E6F6E2C20696163756C697320666175636962757320746F72746F722E2050726F696E20737573636970697420756C74726963696573206D616C6573756164612E204475697320746F72746F7220656C69742C2064696374756D2071756973207472697374697175652065752C20756C7472696365732061742072697375732E204D6F72626920612065737420696D70657264696574206D6920756C6C616D636F7270657220616C6971756574207375736369706974206E6563206C6F72656D2E2041656E65616E2071756973206C656F206D6F6C6C69732C2076756C70757461746520656C6974207661726975732C20636F6E73657175617420656E696D2E204E756C6C6120756C74726963657320747572706973206A7573746F2C20657420706F73756572652075726E6120636F6E7365637465747572206E65632E2050726F696E206E6F6E20636F6E76616C6C6973206D657475732E20446F6E65632074656D706F7220697073756D20696E206D617572697320636F6E67756520736F6C6C696369747564696E2E20566573746962756C756D20616E746520697073756D207072696D697320696E206661756369627573206F726369206C756374757320657420756C74726963657320706F737565726520637562696C69612043757261653B2053757370656E646973736520636F6E76616C6C69732073656D2076656C206D617373612066617563696275732C2065676574206C6163696E6961206C616375732074656D706F722E204E756C6C61207175697320756C747269636965732070757275732E2050726F696E20617563746F722072686F6E637573206E69626820636F6E64696D656E74756D206D6F6C6C69732E20416C697175616D20636F6E73657175617420656E696D206174206D65747573206C75637475732C206120656C656966656E6420707572757320656765737461732E20437572616269747572206174206E696268206D657475732E204E616D20626962656E64756D2C206E6571756520617420617563746F72207472697374697175652C206C6F72656D206C696265726F20616C697175657420617263752C206E6F6E20696E74657264756D2074656C6C7573206C65637475732073697420616D65742065726F732E20437261732072686F6E6375732C206D65747573206163206F726E617265206375727375732C20646F6C6F72206A7573746F20756C747269636573206D657475732C20617420756C6C616D636F7270657220766F6C7574706174" + + } + + def "encode bigint"() { + expect: + toHexString(RLP.encodeBigInt(BigInteger.valueOf(input))) == output + + where: + input || output + 0 || "80" + 1 || "01" + 127 || "7F" + 128 || "8180" + 256 || "820100" + 1024 || "820400" + 0xFFFFFF || "83FFFFFF" + 0xFFFFFFFFFFFF || "86FFFFFFFFFFFF" + } + + def "encode string list"() { + expect: + toHexString(RLP.encodeStringList("aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo")) == "F83C836161618362626283636363836464648365656583666666836767678368686883696969836A6A6A836B6B6B836C6C6C836D6D6D836E6E6E836F6F6F" + } + + def longByteArray() { + def manyBytes = new byte[57] + manyBytes[0] = 1 + manyBytes[1] = 2 + manyBytes[2] = 3 + return manyBytes + } +} diff --git a/src/test/resources/blocks/eth_valid_block_1.json b/src/test/resources/blocks/eth_valid_block_1.json new file mode 100644 index 00000000..9d3418e3 --- /dev/null +++ b/src/test/resources/blocks/eth_valid_block_1.json @@ -0,0 +1,18 @@ +{ + "hash": "0xae174c5cf816f820b17ae13d120fff057406ce12f448422f0d522459d4ae646b", + "parentHash": "0xd783efa4d392943503f28438ad5830b2d5964696ffc285f338585e9fe0a37a05", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0xc0ea08a2d404d3172d2add29a45be56da40e2949", + "stateRoot": "0x77d14e10470b5850332524f8cd6f69ad21f070ce92dca33ab2858300242ef2f1", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "difficulty": "0x98BA212B716C", + "number": "0x3285D2", + "gasLimit": "0x3D4642", + "gasUsed": "0x0", + "timestamp": "0x58BF4098", + "mixHash": "0x3e140b0784516af5e5ec6730f2fb20cca22f32be399b9e4ad77d32541f798cd0", + "nonce": "0xf400cd0006070c49", + "extraData": "0x7777772E62772E636F6D", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/src/test/resources/blocks/eth_valid_block_2.json b/src/test/resources/blocks/eth_valid_block_2.json new file mode 100644 index 00000000..90cb25c5 --- /dev/null +++ b/src/test/resources/blocks/eth_valid_block_2.json @@ -0,0 +1,22 @@ +{ + "baseFeePerGas": "0x1bee2dd60", + "difficulty": "0x2a392ce7577bb6", + "extraData": "0x706f6f6c696e2e636f6d203bb704c2ea57e9ea", + "gasLimit": "0x1c95111", + "gasUsed": "0x1c92201", + "hash": "0x95fd7b85ed04cc67884d7c33d13df3ebb568a2c1532021316c296038bda71776", + "logsBloom": "0xcebecdfb7deb7ced9da83f9dfbbadf778315b7cb669d85bee9af5a76dcaebdbf9f7eb5ed9dd15d1ff4a1bb6f67ff4bdc4f1db5a95efbbbf4a655b2ff6d3ff7fde4fb98b99f59b5fefa921fd962296df2bcdbdfbf8ff55d7d12c2df1fbcbddadc5f12abc5e72af47ec084be69a17a3fdbff7dea6fff7d5ff60fdbecd2cf6cbebdcf789f4ce95fbbe416ef9bffcf6ea4f65fa9adf3fb6bdd1a7efffbfe49bbbbb5fbddab6df5ea7e689fa9ebf6ed76bdb497dc2bceff39f31a1e33fff7dcfd637cdb53f70fb3cf0cfb6ff0fbbebdfbd7dcaffaff46ea5f8b9e6e757ffefcfdf7dea17f7f7cffef7f4d5ea055fd7f6c7cafaeeb98b9496d0e5df7eedab1be5b79ef", + "miner": "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7", + "mixHash": "0x1071fcdfa97b5a7a7c65ff9376ad212815d89986c1530c8b23f84d31c13cad12", + "nonce": "0xe6de1dde14d49aeb", + "number": "0xeaf0bb", + "parentHash": "0x431533a3fec65477d91c310a78ac766c2c660b4c410db6c429cb71ca069cc441", + "receiptsRoot": "0x01253f659cdf7e7223ef4b254fa7e654c1813bb1f39676033ff34b18c65ccb83", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x230b2", + "stateRoot": "0x482376d49e5a49695f28fbc43cadb7c09ec1486d0951280ca1d92590b5b83c8d", + "timestamp": "0x6304e043", + "totalDifficulty": "0xc1370904c375c59ceb9", + "transactionsRoot": "0x7ada19c3ef58d5470452dfa2a45611c632e36204b90a77da0419225f0364f713", + "uncles": [] +} From f3e75d9680c7ae672ec62ef84f49c1229a9e6485 Mon Sep 17 00:00:00 2001 From: Maxksim Fomenkov Date: Wed, 24 Aug 2022 09:56:31 +0300 Subject: [PATCH 2/4] fix linter issues --- src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt | 1 - .../kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt | 2 +- .../dshackle/upstream/ethereum/DefaultEthereumHead.kt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt index 6dd43d35..01890f44 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -16,7 +16,6 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.upstream.ethereum.EthereumBlockValidator import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import org.slf4j.LoggerFactory import reactor.core.Disposable diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt index 3e7b61b7..276f6bfe 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt @@ -13,4 +13,4 @@ interface BlockValidator { companion object { val ALWAYS_VALID = AlwaysValid() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt index 1432f994..16c5d98b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt @@ -19,7 +19,6 @@ import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.AbstractHead -import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest From 7eea1cc6f171759b262b42df9df849e4243e6562 Mon Sep 17 00:00:00 2001 From: Maxksim Fomenkov Date: Thu, 25 Aug 2022 13:40:40 +0300 Subject: [PATCH 3/4] added total difficulty & blocks sequence checks --- .../dshackle/upstream/AbstractHead.kt | 2 +- .../dshackle/upstream/BlockValidator.kt | 4 +- .../ethereum/EthereumBlockValidator.kt | 104 ++++++++++++++++-- .../EthereumBlockValidatorSpec.groovy | 48 ++++++-- .../resources/blocks/eth_block_sec_1.json | 23 ++++ .../resources/blocks/eth_block_sec_2.json | 23 ++++ .../blocks/eth_block_sec_2_invalid.json | 23 ++++ .../resources/blocks/eth_valid_block_2.json | 22 ---- 8 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 src/test/resources/blocks/eth_block_sec_1.json create mode 100644 src/test/resources/blocks/eth_block_sec_2.json create mode 100644 src/test/resources/blocks/eth_block_sec_2_invalid.json delete mode 100644 src/test/resources/blocks/eth_valid_block_2.json diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt index 01890f44..22964d0c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -57,7 +57,7 @@ abstract class AbstractHead( } .subscribeOn(Schedulers.boundedElastic()) .subscribe { block -> - if (blockValidator.isValid(block)) { + if (blockValidator.isValid(forkChoice.getHead(), block)) { notifyBeforeBlock() when (val choiceResult = forkChoice.choose(block)) { is ForkChoice.ChoiceResult.Updated -> { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt index 276f6bfe..9a3ad201 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BlockValidator.kt @@ -4,10 +4,10 @@ import io.emeraldpay.dshackle.data.BlockContainer interface BlockValidator { - fun isValid(block: BlockContainer): Boolean + fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean class AlwaysValid : BlockValidator { - override fun isValid(block: BlockContainer): Boolean = true + override fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean = true } companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt index 03374716..d98ddf4b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt @@ -10,13 +10,16 @@ import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.encodeBigInt import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.encodeList import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexString import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexStringI +import io.emeraldpay.etherjar.rpc.json.BlockJson import org.bouncycastle.jcajce.provider.digest.Keccak import org.slf4j.LoggerFactory +import java.math.BigDecimal import java.math.BigInteger class EthereumBlockValidator : BlockValidator { companion object { + val ACCURACY = 0.02.toBigDecimal() val MAX_BIG_INT: BigInteger = BigInteger.valueOf(2).pow(256) val NUMBER_ELEMENTS = setOf("difficulty", "number", "gasLimit", "gasUsed", "timestamp", "baseFeePerGas") val ELEMENTS = setOf( @@ -41,22 +44,22 @@ class EthereumBlockValidator : BlockValidator { private val log = LoggerFactory.getLogger(EthereumBlockValidator::class.java) } - override fun isValid(block: BlockContainer): Boolean = - block.json?.let { + override fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean = + newHead.json?.let { println(String(it)) + val validBlocksSequence = currentHead?.let { cur -> validateTotalDifficulty(cur, newHead) } ?: true val node = Global.objectMapper.readTree(it) val rlpEncoded = rlp(node) - val hashValid = validateHash(block.hash, rlpEncoded) - + val hashValid = validateHash(newHead.hash, rlpEncoded) if (!hashValid) { - log.warn("Hash not valid for block ${block.hash}") + log.warn("Hash '${newHead.hash}' not valid for block ${newHead.hash}") } val difficultyValid = validateDifficulty(node, rlpEncoded) if (!difficultyValid) { - log.warn("PoW not valid for block ${block.hash}") + log.warn("PoW not valid for block ${newHead.hash}") } - hashValid && difficultyValid + hashValid && difficultyValid && validBlocksSequence } ?: false private fun rlp(node: JsonNode): Map = @@ -77,8 +80,11 @@ class EthereumBlockValidator : BlockValidator { } private fun validateDifficulty(node: JsonNode, rlpEncoded: Map): Boolean { - val nonce = fromHexStringI(node["nonce"].asText()) val difficulty = fromHexStringI(node["difficulty"].asText()) + if (difficulty.signum() < 0) { + return false + } + val nonce = fromHexStringI(node["nonce"].asText()) val mix = fromHexString(node["mixHash"].asText()) val target = MAX_BIG_INT.divide(difficulty) @@ -87,14 +93,90 @@ class EthereumBlockValidator : BlockValidator { .mapNotNull { rlpEncoded[it] } .toList() ) - val sha3 = sha3(encoded) - val seed = Keccak.Digest512().digest(sha3.plus(nonce.asUint64())) + val headerHash = sha3(encoded) + val seed = Keccak.Digest512().digest(headerHash.plus(nonce.asUint64())) val result = sha3(seed.plus(mix)) return target >= BigInteger(1, result) } + private fun validateTotalDifficulty(currentHead: BlockContainer, newHead: BlockContainer): Boolean = + currentHead.getParsed(BlockJson::class.java)?.let { cur -> + newHead.getParsed(BlockJson::class.java)?.let { + val numberValid = it.number >= cur.number + if (!numberValid) { + log.warn( + "Block number {} not valid for {}. Must be greater than {}", + it.number, + it.hash, + cur.number + ) + } + + val timestampValid = it.timestamp > cur.timestamp + if (!timestampValid) { + log.warn( + "Block timestamp {} not valid for {}. Must be greater than {}", + it.timestamp, + it.hash, + cur.timestamp + ) + } + + numberValid && timestampValid && approxTotalDifficultyValid( + curTotalDifficulty = cur.totalDifficulty, + curDifficulty = cur.difficulty, + curNumber = cur.number, + blockTotalDifficulty = it.totalDifficulty, + blockNumber = it.number, + blockHash = it.hash.toHex() + ) + } ?: false + } ?: true + + private fun approxTotalDifficultyValid( + curTotalDifficulty: BigInteger, + curDifficulty: BigInteger, + curNumber: Long, + blockTotalDifficulty: BigInteger, + blockNumber: Long, + blockHash: String + ): Boolean { + if (blockTotalDifficulty < curTotalDifficulty) { + log.warn( + "Block totalDifficulty {} not valid for {}. Must be greater than {}", + blockTotalDifficulty, + blockHash, + curTotalDifficulty + ) + return false + } + + val decimalRef = curDifficulty.toBigDecimal() + val shift = BigDecimal.valueOf(blockNumber - curNumber) + val totalAccuracy = shift * ACCURACY + + val blockApprox = (blockTotalDifficulty - curTotalDifficulty).toBigDecimal() / shift + val min = decimalRef * (BigDecimal.ONE - totalAccuracy) + val max = decimalRef * (BigDecimal.ONE + totalAccuracy) + + val valid = blockApprox in min..max + if (!valid) { + log.warn( + "Block totalDifficulty {} not valid for {}. Expected to be int [{}, {}]", + blockTotalDifficulty, + blockHash, + min.toBigInteger(), + max.toBigInteger() + ) + } + return valid + } + private fun BigInteger.asUint64() = this.toByteArray().let { bytes -> - ByteArray(8) { bytes[bytes.size - it - 1] } + ByteArray(8) { + val index = bytes.size - it - 1 + if (index < bytes.size) bytes[index] else 0 + } } private fun sha3(byteArray: ByteArray): ByteArray = diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy index dc6cf739..d52201f8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidatorSpec.groovy @@ -1,29 +1,57 @@ package io.emeraldpay.dshackle.upstream.ethereum -import com.fasterxml.jackson.databind.JsonNode + import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.etherjar.rpc.json.BlockJson import org.apache.commons.io.FileUtils import spock.lang.Specification -import java.time.Instant - class EthereumBlockValidatorSpec extends Specification { + def mapper = Global.getObjectMapper() def validator = new EthereumBlockValidator() def "test validate block"() { expect: def path = "src/test/resources/blocks/${file}.json" - def bytes = FileUtils.readFileToByteArray(new File(path)) - - def container = new BlockContainer(1L, BlockId.from(hash), BigInteger.ONE, Instant.now(), false, bytes, null, Collections.emptyList(), 1) - validator.isValid(container) == expect + validator.isValid(null, fromFile(path)) == expect where: - file || expect || hash - "eth_valid_block_1" || true || "0xae174c5cf816f820b17ae13d120fff057406ce12f448422f0d522459d4ae646b" - "eth_valid_block_2" || true || "0x95fd7b85ed04cc67884d7c33d13df3ebb568a2c1532021316c296038bda71776" + file || expect + "eth_valid_block_1" || true + "eth_block_sec_1" || true + "eth_block_sec_2" || true + "eth_block_sec_2_invalid" || true + } + + def "test validate blocks sequence"() { + expect: + def curPath = "src/test/resources/blocks/${cur}.json" + def nextPath = "src/test/resources/blocks/${next}.json" + + validator.isValid(fromFile(curPath), fromFile(nextPath)) == expect + + where: + cur || next || expect + "eth_block_sec_1" || "eth_block_sec_2" || true + "eth_block_sec_2" || "eth_block_sec_1" || false + "eth_block_sec_1" || "eth_block_sec_2_invalid" || false + } + + def fromFile(String path) { + def bytes = FileUtils.readFileToByteArray(new File(path)) + def block = mapper.readValue(bytes, BlockJson.class) + + return new BlockContainer( + block.number, + BlockId.from(block.hash.toHex()), + block.difficulty, + block.timestamp, + true, bytes, + block, + Collections.emptyList(), + 1) } } diff --git a/src/test/resources/blocks/eth_block_sec_1.json b/src/test/resources/blocks/eth_block_sec_1.json new file mode 100644 index 00000000..2c1c589d --- /dev/null +++ b/src/test/resources/blocks/eth_block_sec_1.json @@ -0,0 +1,23 @@ +{ + "baseFeePerGas": "0x121e9f5ec", + "difficulty": "0x2a76b9975fb402", + "extraData": "0x457468657265756d50504c4e532f326d696e6572735f455536", + "gasLimit": "0x1c9c380", + "gasUsed": "0x1c995f7", + "hash": "0xb359a505ab41388cb97f3d7a270f9a5a70ee46f3b496c5f5f367f3b13c6858bf", + "logsBloom": "0x37fbc56e6b8fd9bef7f7ffd0ff7557f386b67de3cd2c433a7a7fdafa3db9c96f7de997ecde4c7f75540e9bf7607f1baebe79bffaae7ffb9ff783dce2b5f7e3e3f75fc8a75bf8a5fff9cf63fb5be179e10ad75eec9f7e9b5b31fbfed7c85749bfff6dc82532ff7fa999dfdddcf7e46efb3a5f2c7f8e5fa7777f23dff6f5ffe60afb359fee6e639e7fd94dafe1eff7a8676f78efc39f7cf3ddadafbffb4eb6b4dbffd263f9ffbfff708ef7ffbbfd56edbdcfefbed6aaae0f9b7b6fdff7feffe67dc7afbcfeedb2ad73ffefbd2edc9f5ceb1f6c37f1b17feeb77b5fdf3f727f69fbb476ffcaf8773de7e7ff3725097c4a8db577de793ede8df472ccde9d7e0aff7e", + "miner": "0x00192fb10df37c9fb26829eb2cc623cd1bf599e8", + "mixHash": "0x61b14f607b1c9ff2aa14e65ce8e87f2831cd37e1a0b46cfeac8e6fb8d35b9cb0", + "nonce": "0x1da600070ccbad45", + "number": "0xeb1db1", + "parentHash": "0x17b1fe61ce7690725af12865b0f63e20c9c5944748cfbba6041362acc8ca25b4", + "receiptsRoot": "0xcec742556a657826cd4d9ceaf00c2a241a28d6145b6e4b6af077820c710930c6", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x2397d", + "stateRoot": "0x2a2773fd8b4d55e2e777bb71adc4d99d913d9520be2026e26dd6c8c22527a204", + "timestamp": "0x63074d3c", + "totalDifficulty": "0xc1b02a29da16ecdfb95", + "transactions": [], + "transactionsRoot": "0xb74a34aeee1e746f60747225b9671d71df22dd368a308ef436059a0e7877c896", + "uncles": [] +} diff --git a/src/test/resources/blocks/eth_block_sec_2.json b/src/test/resources/blocks/eth_block_sec_2.json new file mode 100644 index 00000000..ebe67325 --- /dev/null +++ b/src/test/resources/blocks/eth_block_sec_2.json @@ -0,0 +1,23 @@ +{ + "baseFeePerGas": "0x1461ffefd", + "difficulty": "0x2a6c5be8f9dc16", + "extraData": "0x4554482e4352415a59504f4f4c2e4f5247", + "gasLimit": "0x1ca35ef", + "gasUsed": "0x1ca11b3", + "hash": "0xc3c93de6e76e5da05df8a48ab15dfb0cc610d21932a0220e797e0adc05118ed8", + "logsBloom": "0xffb4778ec39a1aeef1b072f2bb1f47bb91266fd117530aeb7fbdda78deee7d6ff39e5ff8b7d83ad061bf1b9e083709ff0abfbe6afbfffdd73e49f77f80ff377d87dfa1de3f7d94bff9f66bbf5060f9ed20d74468ffff9c29ff07df4de9fdbffa1eed92e043fbc7dbf95efff9aef7ace1bace1e7a94de65e7527beed2454deeceeb609fbc9256d1fbbf6dbee5975335775f72fcd5e7e2585bad718cd278bbf66cefdbefe17acb76375befcbf9fd9ceb9d57d6fbe6ef7ae03fcb7b3eeed9fde946ffb9f5be4d8becf76f6c2633c73ed7c20df1f6e29c7e5bff1f9c79365e3d6dd5bc7afbbbcd7e1afbe5b657f8ab66ac768ee6ace959f9a77f78f9bf95babbffa7", + "miner": "0x4f9bebe3adc3c7f647c0023c60f91ac9dffa52d5", + "mixHash": "0xf6d6b09cae8efa94f2caa018c38c5c17ab1d89ab3d84feca076d1894f2f07a8f", + "nonce": "0xf7d487f95819f012", + "number": "0xeb1db2", + "parentHash": "0xb359a505ab41388cb97f3d7a270f9a5a70ee46f3b496c5f5f367f3b13c6858bf", + "receiptsRoot": "0x4e3cd00367e2a59339612ed2d10dcec5ec9ccdcbeb66a343bf1a8a115b9ce04e", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x2086f", + "stateRoot": "0x8e2f50554d5e8bb783f12db999b5ff8a948e980a80593e877574eaa089ad362b", + "timestamp": "0x63074d58", + "totalDifficulty": "0xc1b02cd09fd57c7d7ab", + "transactions": [], + "transactionsRoot": "0x22b3c3ce7398ea4ee2665a5f13a7b77c4828802f160fc261f82b551f0a37a97c", + "uncles": [] +} diff --git a/src/test/resources/blocks/eth_block_sec_2_invalid.json b/src/test/resources/blocks/eth_block_sec_2_invalid.json new file mode 100644 index 00000000..44565225 --- /dev/null +++ b/src/test/resources/blocks/eth_block_sec_2_invalid.json @@ -0,0 +1,23 @@ +{ + "baseFeePerGas": "0x1461ffefd", + "difficulty": "0x2a6c5be8f9dc16", + "extraData": "0x4554482e4352415a59504f4f4c2e4f5247", + "gasLimit": "0x1ca35ef", + "gasUsed": "0x1ca11b3", + "hash": "0xc3c93de6e76e5da05df8a48ab15dfb0cc610d21932a0220e797e0adc05118ed8", + "logsBloom": "0xffb4778ec39a1aeef1b072f2bb1f47bb91266fd117530aeb7fbdda78deee7d6ff39e5ff8b7d83ad061bf1b9e083709ff0abfbe6afbfffdd73e49f77f80ff377d87dfa1de3f7d94bff9f66bbf5060f9ed20d74468ffff9c29ff07df4de9fdbffa1eed92e043fbc7dbf95efff9aef7ace1bace1e7a94de65e7527beed2454deeceeb609fbc9256d1fbbf6dbee5975335775f72fcd5e7e2585bad718cd278bbf66cefdbefe17acb76375befcbf9fd9ceb9d57d6fbe6ef7ae03fcb7b3eeed9fde946ffb9f5be4d8becf76f6c2633c73ed7c20df1f6e29c7e5bff1f9c79365e3d6dd5bc7afbbbcd7e1afbe5b657f8ab66ac768ee6ace959f9a77f78f9bf95babbffa7", + "miner": "0x4f9bebe3adc3c7f647c0023c60f91ac9dffa52d5", + "mixHash": "0xf6d6b09cae8efa94f2caa018c38c5c17ab1d89ab3d84feca076d1894f2f07a8f", + "nonce": "0xf7d487f95819f012", + "number": "0xeb1db2", + "parentHash": "0xb359a505ab41388cb97f3d7a270f9a5a70ee46f3b496c5f5f367f3b13c6858bf", + "receiptsRoot": "0x4e3cd00367e2a59339612ed2d10dcec5ec9ccdcbeb66a343bf1a8a115b9ce04e", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x2086f", + "stateRoot": "0x8e2f50554d5e8bb783f12db999b5ff8a948e980a80593e877574eaa089ad362b", + "timestamp": "0x63074d58", + "totalDifficulty": "0xc1b03cd09fd57c7d7ab", + "transactions": [], + "transactionsRoot": "0x22b3c3ce7398ea4ee2665a5f13a7b77c4828802f160fc261f82b551f0a37a97c", + "uncles": [] +} diff --git a/src/test/resources/blocks/eth_valid_block_2.json b/src/test/resources/blocks/eth_valid_block_2.json deleted file mode 100644 index 90cb25c5..00000000 --- a/src/test/resources/blocks/eth_valid_block_2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "baseFeePerGas": "0x1bee2dd60", - "difficulty": "0x2a392ce7577bb6", - "extraData": "0x706f6f6c696e2e636f6d203bb704c2ea57e9ea", - "gasLimit": "0x1c95111", - "gasUsed": "0x1c92201", - "hash": "0x95fd7b85ed04cc67884d7c33d13df3ebb568a2c1532021316c296038bda71776", - "logsBloom": "0xcebecdfb7deb7ced9da83f9dfbbadf778315b7cb669d85bee9af5a76dcaebdbf9f7eb5ed9dd15d1ff4a1bb6f67ff4bdc4f1db5a95efbbbf4a655b2ff6d3ff7fde4fb98b99f59b5fefa921fd962296df2bcdbdfbf8ff55d7d12c2df1fbcbddadc5f12abc5e72af47ec084be69a17a3fdbff7dea6fff7d5ff60fdbecd2cf6cbebdcf789f4ce95fbbe416ef9bffcf6ea4f65fa9adf3fb6bdd1a7efffbfe49bbbbb5fbddab6df5ea7e689fa9ebf6ed76bdb497dc2bceff39f31a1e33fff7dcfd637cdb53f70fb3cf0cfb6ff0fbbebdfbd7dcaffaff46ea5f8b9e6e757ffefcfdf7dea17f7f7cffef7f4d5ea055fd7f6c7cafaeeb98b9496d0e5df7eedab1be5b79ef", - "miner": "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7", - "mixHash": "0x1071fcdfa97b5a7a7c65ff9376ad212815d89986c1530c8b23f84d31c13cad12", - "nonce": "0xe6de1dde14d49aeb", - "number": "0xeaf0bb", - "parentHash": "0x431533a3fec65477d91c310a78ac766c2c660b4c410db6c429cb71ca069cc441", - "receiptsRoot": "0x01253f659cdf7e7223ef4b254fa7e654c1813bb1f39676033ff34b18c65ccb83", - "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x230b2", - "stateRoot": "0x482376d49e5a49695f28fbc43cadb7c09ec1486d0951280ca1d92590b5b83c8d", - "timestamp": "0x6304e043", - "totalDifficulty": "0xc1370904c375c59ceb9", - "transactionsRoot": "0x7ada19c3ef58d5470452dfa2a45611c632e36204b90a77da0419225f0364f713", - "uncles": [] -} From eaeba385ffd27e84cdd1ec81a9b30ea1632bb004 Mon Sep 17 00:00:00 2001 From: Maxksim Fomenkov Date: Thu, 25 Aug 2022 13:41:19 +0300 Subject: [PATCH 4/4] removed debug --- .../dshackle/upstream/ethereum/EthereumBlockValidator.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt index d98ddf4b..25a84293 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt @@ -46,7 +46,6 @@ class EthereumBlockValidator : BlockValidator { override fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean = newHead.json?.let { - println(String(it)) val validBlocksSequence = currentHead?.let { cur -> validateTotalDifficulty(cur, newHead) } ?: true val node = Global.objectMapper.readTree(it) val rlpEncoded = rlp(node)