added total difficulty & blocks sequence checks
This commit is contained in:
@@ -57,7 +57,7 @@ abstract class AbstractHead(
|
|||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.boundedElastic())
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.subscribe { block ->
|
.subscribe { block ->
|
||||||
if (blockValidator.isValid(block)) {
|
if (blockValidator.isValid(forkChoice.getHead(), block)) {
|
||||||
notifyBeforeBlock()
|
notifyBeforeBlock()
|
||||||
when (val choiceResult = forkChoice.choose(block)) {
|
when (val choiceResult = forkChoice.choose(block)) {
|
||||||
is ForkChoice.ChoiceResult.Updated -> {
|
is ForkChoice.ChoiceResult.Updated -> {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import io.emeraldpay.dshackle.data.BlockContainer
|
|||||||
|
|
||||||
interface BlockValidator {
|
interface BlockValidator {
|
||||||
|
|
||||||
fun isValid(block: BlockContainer): Boolean
|
fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean
|
||||||
|
|
||||||
class AlwaysValid : BlockValidator {
|
class AlwaysValid : BlockValidator {
|
||||||
override fun isValid(block: BlockContainer): Boolean = true
|
override fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -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.encodeList
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexString
|
import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexString
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.RLP.Companion.fromHexStringI
|
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.bouncycastle.jcajce.provider.digest.Keccak
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
class EthereumBlockValidator : BlockValidator {
|
class EthereumBlockValidator : BlockValidator {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val ACCURACY = 0.02.toBigDecimal()
|
||||||
val MAX_BIG_INT: BigInteger = BigInteger.valueOf(2).pow(256)
|
val MAX_BIG_INT: BigInteger = BigInteger.valueOf(2).pow(256)
|
||||||
val NUMBER_ELEMENTS = setOf("difficulty", "number", "gasLimit", "gasUsed", "timestamp", "baseFeePerGas")
|
val NUMBER_ELEMENTS = setOf("difficulty", "number", "gasLimit", "gasUsed", "timestamp", "baseFeePerGas")
|
||||||
val ELEMENTS = setOf(
|
val ELEMENTS = setOf(
|
||||||
@@ -41,22 +44,22 @@ class EthereumBlockValidator : BlockValidator {
|
|||||||
private val log = LoggerFactory.getLogger(EthereumBlockValidator::class.java)
|
private val log = LoggerFactory.getLogger(EthereumBlockValidator::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isValid(block: BlockContainer): Boolean =
|
override fun isValid(currentHead: BlockContainer?, newHead: BlockContainer): Boolean =
|
||||||
block.json?.let {
|
newHead.json?.let {
|
||||||
println(String(it))
|
println(String(it))
|
||||||
|
val validBlocksSequence = currentHead?.let { cur -> validateTotalDifficulty(cur, newHead) } ?: true
|
||||||
val node = Global.objectMapper.readTree(it)
|
val node = Global.objectMapper.readTree(it)
|
||||||
val rlpEncoded = rlp(node)
|
val rlpEncoded = rlp(node)
|
||||||
val hashValid = validateHash(block.hash, rlpEncoded)
|
val hashValid = validateHash(newHead.hash, rlpEncoded)
|
||||||
|
|
||||||
if (!hashValid) {
|
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)
|
val difficultyValid = validateDifficulty(node, rlpEncoded)
|
||||||
if (!difficultyValid) {
|
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
|
} ?: false
|
||||||
|
|
||||||
private fun rlp(node: JsonNode): Map<String, ByteArray> =
|
private fun rlp(node: JsonNode): Map<String, ByteArray> =
|
||||||
@@ -77,8 +80,11 @@ class EthereumBlockValidator : BlockValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun validateDifficulty(node: JsonNode, rlpEncoded: Map<String, ByteArray>): Boolean {
|
private fun validateDifficulty(node: JsonNode, rlpEncoded: Map<String, ByteArray>): Boolean {
|
||||||
val nonce = fromHexStringI(node["nonce"].asText())
|
|
||||||
val difficulty = fromHexStringI(node["difficulty"].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 mix = fromHexString(node["mixHash"].asText())
|
||||||
|
|
||||||
val target = MAX_BIG_INT.divide(difficulty)
|
val target = MAX_BIG_INT.divide(difficulty)
|
||||||
@@ -87,14 +93,90 @@ class EthereumBlockValidator : BlockValidator {
|
|||||||
.mapNotNull { rlpEncoded[it] }
|
.mapNotNull { rlpEncoded[it] }
|
||||||
.toList()
|
.toList()
|
||||||
)
|
)
|
||||||
val sha3 = sha3(encoded)
|
val headerHash = sha3(encoded)
|
||||||
val seed = Keccak.Digest512().digest(sha3.plus(nonce.asUint64()))
|
val seed = Keccak.Digest512().digest(headerHash.plus(nonce.asUint64()))
|
||||||
val result = sha3(seed.plus(mix))
|
val result = sha3(seed.plus(mix))
|
||||||
return target >= BigInteger(1, result)
|
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 ->
|
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 =
|
private fun sha3(byteArray: ByteArray): ByteArray =
|
||||||
|
|||||||
@@ -1,29 +1,57 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.data.BlockId
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
|
import io.emeraldpay.etherjar.rpc.json.BlockJson
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
class EthereumBlockValidatorSpec extends Specification {
|
class EthereumBlockValidatorSpec extends Specification {
|
||||||
|
|
||||||
|
def mapper = Global.getObjectMapper()
|
||||||
def validator = new EthereumBlockValidator()
|
def validator = new EthereumBlockValidator()
|
||||||
|
|
||||||
def "test validate block"() {
|
def "test validate block"() {
|
||||||
expect:
|
expect:
|
||||||
def path = "src/test/resources/blocks/${file}.json"
|
def path = "src/test/resources/blocks/${file}.json"
|
||||||
def bytes = FileUtils.readFileToByteArray(new File(path))
|
validator.isValid(null, fromFile(path)) == expect
|
||||||
|
|
||||||
def container = new BlockContainer(1L, BlockId.from(hash), BigInteger.ONE, Instant.now(), false, bytes, null, Collections.emptyList(), 1)
|
|
||||||
validator.isValid(container) == expect
|
|
||||||
|
|
||||||
where:
|
where:
|
||||||
file || expect || hash
|
file || expect
|
||||||
"eth_valid_block_1" || true || "0xae174c5cf816f820b17ae13d120fff057406ce12f448422f0d522459d4ae646b"
|
"eth_valid_block_1" || true
|
||||||
"eth_valid_block_2" || true || "0x95fd7b85ed04cc67884d7c33d13df3ebb568a2c1532021316c296038bda71776"
|
"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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/test/resources/blocks/eth_block_sec_1.json
Normal file
23
src/test/resources/blocks/eth_block_sec_1.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
23
src/test/resources/blocks/eth_block_sec_2.json
Normal file
23
src/test/resources/blocks/eth_block_sec_2.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
23
src/test/resources/blocks/eth_block_sec_2_invalid.json
Normal file
23
src/test/resources/blocks/eth_block_sec_2_invalid.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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": []
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user