add Ethereum block validation for HEAD subscription
This commit is contained in:
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String, ByteArray> =
|
||||
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<String, ByteArray>): 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<String, ByteArray>): 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)
|
||||
}
|
||||
102
src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt
Normal file
102
src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/RLP.kt
Normal file
@@ -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>): 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
|
||||
}
|
||||
@@ -132,7 +132,7 @@ class AbstractHeadSpec extends Specification {
|
||||
BlockContainer getHead() {
|
||||
return null
|
||||
}
|
||||
})
|
||||
}, new BlockValidator.AlwaysValid())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
18
src/test/resources/blocks/eth_valid_block_1.json
Normal file
18
src/test/resources/blocks/eth_valid_block_1.json
Normal file
@@ -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"
|
||||
}
|
||||
22
src/test/resources/blocks/eth_valid_block_2.json
Normal file
22
src/test/resources/blocks/eth_valid_block_2.json
Normal file
@@ -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": []
|
||||
}
|
||||
Reference in New Issue
Block a user