mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
backport from test_blockchain_json, see issue #666
This commit is contained in:
parent
cbed4a368b
commit
ce8e5511e3
@ -2,51 +2,11 @@ import options, sets,
|
|||||||
eth/[common, bloom, trie/db], chronicles, nimcrypto,
|
eth/[common, bloom, trie/db], chronicles, nimcrypto,
|
||||||
../db/[db_chain, accounts_cache],
|
../db/[db_chain, accounts_cache],
|
||||||
../utils, ../constants, ../transaction,
|
../utils, ../constants, ../transaction,
|
||||||
../vm_state, ../vm_types, ../vm_state_transactions,
|
../vm_state, ../vm_types,
|
||||||
../vm_computation, ../vm_message, ../vm_precompiles,
|
|
||||||
../vm_types2,
|
../vm_types2,
|
||||||
./dao, ../config,
|
./dao, ./validate, ../config,
|
||||||
../transaction/call_evm
|
../transaction/call_evm
|
||||||
|
|
||||||
proc validateTransaction*(vmState: BaseVMState, tx: Transaction,
|
|
||||||
sender: EthAddress, fork: Fork): bool =
|
|
||||||
let balance = vmState.readOnlyStateDB.getBalance(sender)
|
|
||||||
let nonce = vmState.readOnlyStateDB.getNonce(sender)
|
|
||||||
|
|
||||||
if vmState.cumulativeGasUsed + tx.gasLimit > vmState.blockHeader.gasLimit:
|
|
||||||
debug "invalid tx: block header gasLimit reached",
|
|
||||||
maxLimit=vmState.blockHeader.gasLimit,
|
|
||||||
gasUsed=vmState.cumulativeGasUsed,
|
|
||||||
addition=tx.gasLimit
|
|
||||||
return
|
|
||||||
|
|
||||||
let gasCost = tx.gasLimit.u256 * tx.gasPrice.u256
|
|
||||||
if gasCost > balance:
|
|
||||||
debug "invalid tx: not enough cash for gas",
|
|
||||||
available=balance,
|
|
||||||
require=gasCost
|
|
||||||
return
|
|
||||||
|
|
||||||
if tx.value > balance - gasCost:
|
|
||||||
debug "invalid tx: not enough cash to send",
|
|
||||||
available=balance,
|
|
||||||
availableMinusGas=balance-gasCost,
|
|
||||||
require=tx.value
|
|
||||||
return
|
|
||||||
|
|
||||||
if tx.gasLimit < tx.intrinsicGas(fork):
|
|
||||||
debug "invalid tx: not enough gas to perform calculation",
|
|
||||||
available=tx.gasLimit,
|
|
||||||
require=tx.intrinsicGas(fork)
|
|
||||||
return
|
|
||||||
|
|
||||||
if tx.nonce != nonce:
|
|
||||||
debug "invalid tx: account nonce mismatch",
|
|
||||||
txNonce=tx.nonce,
|
|
||||||
accountNonce=nonce
|
|
||||||
return
|
|
||||||
|
|
||||||
result = true
|
|
||||||
|
|
||||||
proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt =
|
proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt =
|
||||||
## Process the transaction, write the results to db.
|
## Process the transaction, write the results to db.
|
||||||
|
345
nimbus/p2p/validate.nim
Normal file
345
nimbus/p2p/validate.nim
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
../constants,
|
||||||
|
../db/[db_chain, accounts_cache],
|
||||||
|
../errors,
|
||||||
|
../transaction,
|
||||||
|
../utils,
|
||||||
|
../utils/header,
|
||||||
|
../vm_state,
|
||||||
|
../vm_types,
|
||||||
|
../vm_types2,
|
||||||
|
chronicles,
|
||||||
|
eth/[common, rlp],
|
||||||
|
eth/trie/trie_defs,
|
||||||
|
ethash,
|
||||||
|
nimcrypto,
|
||||||
|
options,
|
||||||
|
sets,
|
||||||
|
stew/endians2,
|
||||||
|
strutils,
|
||||||
|
tables,
|
||||||
|
times
|
||||||
|
|
||||||
|
type
|
||||||
|
MiningHeader = object
|
||||||
|
parentHash : Hash256
|
||||||
|
ommersHash : Hash256
|
||||||
|
coinbase : EthAddress
|
||||||
|
stateRoot : Hash256
|
||||||
|
txRoot : Hash256
|
||||||
|
receiptRoot : Hash256
|
||||||
|
bloom : common.BloomFilter
|
||||||
|
difficulty : DifficultyInt
|
||||||
|
blockNumber : BlockNumber
|
||||||
|
gasLimit : GasInt
|
||||||
|
gasUsed : GasInt
|
||||||
|
timestamp : EthTime
|
||||||
|
extraData : Blob
|
||||||
|
|
||||||
|
Hash512 = MDigest[512]
|
||||||
|
|
||||||
|
const
|
||||||
|
CACHE_MAX_ITEMS = 10
|
||||||
|
|
||||||
|
var cacheByEpoch = initOrderedTable[uint64, seq[Hash512]]()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc mkCacheBytes(blockNumber: uint64): seq[Hash512] =
|
||||||
|
mkcache(getCacheSize(blockNumber), getSeedhash(blockNumber))
|
||||||
|
|
||||||
|
|
||||||
|
proc getCache(blockNumber: uint64): seq[Hash512] =
|
||||||
|
# TODO: this is very inefficient
|
||||||
|
let epochIndex = blockNumber div EPOCH_LENGTH
|
||||||
|
|
||||||
|
# Get the cache if already generated, marking it as recently used
|
||||||
|
if epochIndex in cacheByEpoch:
|
||||||
|
let c = cacheByEpoch[epochIndex]
|
||||||
|
cacheByEpoch.del(epochIndex) # pop and append at end
|
||||||
|
cacheByEpoch[epochIndex] = c
|
||||||
|
return c
|
||||||
|
|
||||||
|
# Generate the cache if it was not already in memory
|
||||||
|
# Simulate requesting mkcache by block number: multiply index by epoch length
|
||||||
|
let c = mkCacheBytes(epochIndex * EPOCH_LENGTH)
|
||||||
|
cacheByEpoch[epochIndex] = c
|
||||||
|
|
||||||
|
# Limit memory usage for cache
|
||||||
|
if cacheByEpoch.len > CACHE_MAX_ITEMS:
|
||||||
|
cacheByEpoch.del(epochIndex)
|
||||||
|
|
||||||
|
shallowCopy(result, c)
|
||||||
|
|
||||||
|
|
||||||
|
func cacheHash(x: openArray[Hash512]): Hash256 =
|
||||||
|
var ctx: keccak256
|
||||||
|
ctx.init()
|
||||||
|
|
||||||
|
for a in x:
|
||||||
|
ctx.update(a.data[0].unsafeAddr, uint(a.data.len))
|
||||||
|
|
||||||
|
ctx.finish result.data
|
||||||
|
ctx.clear()
|
||||||
|
|
||||||
|
|
||||||
|
proc checkPOW(blockNumber: Uint256, miningHash, mixHash: Hash256,
|
||||||
|
nonce: BlockNonce, difficulty: DifficultyInt) =
|
||||||
|
let blockNumber = blockNumber.truncate(uint64)
|
||||||
|
let cache = blockNumber.getCache()
|
||||||
|
|
||||||
|
let size = getDataSize(blockNumber)
|
||||||
|
let miningOutput = hashimotoLight(
|
||||||
|
size, cache, miningHash, uint64.fromBytesBE(nonce))
|
||||||
|
if miningOutput.mixDigest != mixHash:
|
||||||
|
echo "actual: ", miningOutput.mixDigest
|
||||||
|
echo "expected: ", mixHash
|
||||||
|
echo "blockNumber: ", blockNumber
|
||||||
|
echo "miningHash: ", miningHash
|
||||||
|
echo "nonce: ", nonce.toHex
|
||||||
|
echo "difficulty: ", difficulty
|
||||||
|
echo "size: ", size
|
||||||
|
echo "cache hash: ", cacheHash(cache)
|
||||||
|
raise newException(ValidationError, "mixHash mismatch")
|
||||||
|
|
||||||
|
let value = Uint256.fromBytesBE(miningOutput.value.data)
|
||||||
|
if value > Uint256.high div difficulty:
|
||||||
|
raise newException(ValidationError, "mining difficulty error")
|
||||||
|
|
||||||
|
|
||||||
|
func toMiningHeader(header: BlockHeader): MiningHeader =
|
||||||
|
result.parentHash = header.parentHash
|
||||||
|
result.ommersHash = header.ommersHash
|
||||||
|
result.coinbase = header.coinbase
|
||||||
|
result.stateRoot = header.stateRoot
|
||||||
|
result.txRoot = header.txRoot
|
||||||
|
result.receiptRoot = header.receiptRoot
|
||||||
|
result.bloom = header.bloom
|
||||||
|
result.difficulty = header.difficulty
|
||||||
|
result.blockNumber = header.blockNumber
|
||||||
|
result.gasLimit = header.gasLimit
|
||||||
|
result.gasUsed = header.gasUsed
|
||||||
|
result.timestamp = header.timestamp
|
||||||
|
result.extraData = header.extraData
|
||||||
|
|
||||||
|
|
||||||
|
func hash(header: MiningHeader): Hash256 =
|
||||||
|
keccakHash(rlp.encode(header))
|
||||||
|
|
||||||
|
|
||||||
|
proc validateSeal(header: BlockHeader) =
|
||||||
|
let miningHeader = header.toMiningHeader
|
||||||
|
let miningHash = miningHeader.hash
|
||||||
|
|
||||||
|
checkPOW(header.blockNumber, miningHash,
|
||||||
|
header.mixDigest, header.nonce, header.difficulty)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Puplic function, extracted from executor
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc validateTransaction*(vmState: BaseVMState, tx: Transaction,
|
||||||
|
sender: EthAddress, fork: Fork): bool =
|
||||||
|
let balance = vmState.readOnlyStateDB.getBalance(sender)
|
||||||
|
let nonce = vmState.readOnlyStateDB.getNonce(sender)
|
||||||
|
|
||||||
|
if vmState.cumulativeGasUsed + tx.gasLimit > vmState.blockHeader.gasLimit:
|
||||||
|
debug "invalid tx: block header gasLimit reached",
|
||||||
|
maxLimit=vmState.blockHeader.gasLimit,
|
||||||
|
gasUsed=vmState.cumulativeGasUsed,
|
||||||
|
addition=tx.gasLimit
|
||||||
|
return
|
||||||
|
|
||||||
|
let gasCost = tx.gasLimit.u256 * tx.gasPrice.u256
|
||||||
|
if gasCost > balance:
|
||||||
|
debug "invalid tx: not enough cash for gas",
|
||||||
|
available=balance,
|
||||||
|
require=gasCost
|
||||||
|
return
|
||||||
|
|
||||||
|
if tx.value > balance - gasCost:
|
||||||
|
debug "invalid tx: not enough cash to send",
|
||||||
|
available=balance,
|
||||||
|
availableMinusGas=balance-gasCost,
|
||||||
|
require=tx.value
|
||||||
|
return
|
||||||
|
|
||||||
|
if tx.gasLimit < tx.intrinsicGas(fork):
|
||||||
|
debug "invalid tx: not enough gas to perform calculation",
|
||||||
|
available=tx.gasLimit,
|
||||||
|
require=tx.intrinsicGas(fork)
|
||||||
|
return
|
||||||
|
|
||||||
|
if tx.nonce != nonce:
|
||||||
|
debug "invalid tx: account nonce mismatch",
|
||||||
|
txNonce=tx.nonce,
|
||||||
|
accountNonce=nonce
|
||||||
|
return
|
||||||
|
|
||||||
|
result = true
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions, extracted from test_blockchain_json
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func validateGasLimit*(gasLimit, parentGasLimit: GasInt) =
|
||||||
|
if gasLimit < GAS_LIMIT_MINIMUM:
|
||||||
|
raise newException(ValidationError, "Gas limit is below minimum")
|
||||||
|
if gasLimit > GAS_LIMIT_MAXIMUM:
|
||||||
|
raise newException(ValidationError, "Gas limit is above maximum")
|
||||||
|
let diff = gasLimit - parentGasLimit
|
||||||
|
if diff > (parentGasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR):
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "Gas limit difference to parent is too big")
|
||||||
|
|
||||||
|
|
||||||
|
proc validateHeader*(header, parentHeader: BlockHeader, checkSeal: bool) =
|
||||||
|
if header.extraData.len > 32:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "BlockHeader.extraData larger than 32 bytes")
|
||||||
|
|
||||||
|
validateGasLimit(header.gasLimit, parentHeader.gasLimit)
|
||||||
|
|
||||||
|
if header.blockNumber != parentHeader.blockNumber + 1:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "Blocks must be numbered consecutively.")
|
||||||
|
|
||||||
|
if header.timestamp.toUnix <= parentHeader.timestamp.toUnix:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "timestamp must be strictly later than parent")
|
||||||
|
|
||||||
|
if checkSeal:
|
||||||
|
validateSeal(header)
|
||||||
|
|
||||||
|
|
||||||
|
func validateUncle*(currBlock, uncle, uncleParent: BlockHeader) =
|
||||||
|
if uncle.blockNumber >= currBlock.blockNumber:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "uncle block number larger than current block number")
|
||||||
|
|
||||||
|
if uncle.blockNumber != uncleParent.blockNumber + 1:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "Uncle number is not one above ancestor's number")
|
||||||
|
|
||||||
|
if uncle.timestamp.toUnix < uncleParent.timestamp.toUnix:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "Uncle timestamp is before ancestor's timestamp")
|
||||||
|
|
||||||
|
if uncle.gasUsed > uncle.gasLimit:
|
||||||
|
raise newException(ValidationError, "Uncle's gas usage is above the limit")
|
||||||
|
|
||||||
|
|
||||||
|
proc validateGasLimit*(chainDB: BaseChainDB, header: BlockHeader) =
|
||||||
|
let parentHeader = chainDB.getBlockHeader(header.parentHash)
|
||||||
|
let (lowBound, highBound) = gasLimitBounds(parentHeader)
|
||||||
|
|
||||||
|
if header.gasLimit < lowBound:
|
||||||
|
raise newException(ValidationError, "The gas limit is too low")
|
||||||
|
elif header.gasLimit > highBound:
|
||||||
|
raise newException(ValidationError, "The gas limit is too high")
|
||||||
|
|
||||||
|
|
||||||
|
proc validateUncles*(chainDB: BaseChainDB,
|
||||||
|
currBlock: EthBlock, checkSeal: bool) =
|
||||||
|
let hasUncles = currBlock.uncles.len > 0
|
||||||
|
let shouldHaveUncles = currBlock.header.ommersHash != EMPTY_UNCLE_HASH
|
||||||
|
|
||||||
|
if not hasUncles and not shouldHaveUncles:
|
||||||
|
# optimization to avoid loading ancestors from DB, since the block has
|
||||||
|
# no uncles
|
||||||
|
return
|
||||||
|
elif hasUncles and not shouldHaveUncles:
|
||||||
|
raise newException(
|
||||||
|
ValidationError,
|
||||||
|
"Block has uncles but header suggests uncles should be empty")
|
||||||
|
elif shouldHaveUncles and not hasUncles:
|
||||||
|
raise newException(
|
||||||
|
ValidationError,
|
||||||
|
"Header suggests block should have uncles but block has none")
|
||||||
|
|
||||||
|
# Check for duplicates
|
||||||
|
var uncleSet = initHashSet[Hash256]()
|
||||||
|
for uncle in currBlock.uncles:
|
||||||
|
let uncleHash = uncle.hash
|
||||||
|
if uncleHash in uncleSet:
|
||||||
|
raise newException(ValidationError, "Block contains duplicate uncles")
|
||||||
|
else:
|
||||||
|
uncleSet.incl uncleHash
|
||||||
|
|
||||||
|
let recentAncestorHashes = chainDB.getAncestorsHashes(
|
||||||
|
MAX_UNCLE_DEPTH + 1, currBlock.header)
|
||||||
|
let recentUncleHashes = chainDB.getUncleHashes(recentAncestorHashes)
|
||||||
|
let blockHash =currBlock.header.hash
|
||||||
|
|
||||||
|
for uncle in currBlock.uncles:
|
||||||
|
let uncleHash = uncle.hash
|
||||||
|
|
||||||
|
if uncleHash == blockHash:
|
||||||
|
raise newException(ValidationError, "Uncle has same hash as block")
|
||||||
|
|
||||||
|
# ensure the uncle has not already been included.
|
||||||
|
if uncleHash in recentUncleHashes:
|
||||||
|
raise newException(ValidationError, "Duplicate uncle")
|
||||||
|
|
||||||
|
# ensure that the uncle is not one of the canonical chain blocks.
|
||||||
|
if uncleHash in recentAncestorHashes:
|
||||||
|
raise newException(ValidationError, "Uncle cannot be an ancestor")
|
||||||
|
|
||||||
|
# ensure that the uncle was built off of one of the canonical chain
|
||||||
|
# blocks.
|
||||||
|
if (uncle.parentHash notin recentAncestorHashes) or
|
||||||
|
(uncle.parentHash == currBlock.header.parentHash):
|
||||||
|
raise newException(ValidationError, "Uncle's parent is not an ancestor")
|
||||||
|
|
||||||
|
# Now perform VM level validation of the uncle
|
||||||
|
if checkSeal:
|
||||||
|
validateSeal(uncle)
|
||||||
|
|
||||||
|
let uncleParent = chainDB.getBlockHeader(uncle.parentHash)
|
||||||
|
validateUncle(currBlock.header, uncle, uncleParent)
|
||||||
|
|
||||||
|
|
||||||
|
func isGenesis*(currBlock: EthBlock): bool =
|
||||||
|
result = currBlock.header.blockNumber == 0.u256 and
|
||||||
|
currBlock.header.parentHash == GENESIS_PARENT_HASH
|
||||||
|
|
||||||
|
|
||||||
|
proc validateBlock*(chainDB: BaseChainDB,
|
||||||
|
currBlock: EthBlock, checkSeal: bool): bool =
|
||||||
|
if currBlock.isGenesis:
|
||||||
|
if currBlock.header.extraData.len > 32:
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "BlockHeader.extraData larger than 32 bytes")
|
||||||
|
return true
|
||||||
|
|
||||||
|
let parentHeader = chainDB.getBlockHeader(currBlock.header.parentHash)
|
||||||
|
validateHeader(currBlock.header, parentHeader, checkSeal)
|
||||||
|
|
||||||
|
if currBlock.uncles.len > MAX_UNCLES:
|
||||||
|
raise newException(ValidationError, "Number of uncles exceed limit.")
|
||||||
|
|
||||||
|
if not chainDB.exists(currBlock.header.stateRoot):
|
||||||
|
raise newException(
|
||||||
|
ValidationError, "`state_root` was not found in the db.")
|
||||||
|
|
||||||
|
validateUncles(chainDB, currBlock, checkSeal)
|
||||||
|
validateGaslimit(chainDB, currBlock.header)
|
||||||
|
|
||||||
|
result = true
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
@ -6,17 +6,17 @@
|
|||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
unittest2, json, os, tables, strutils, sets, strformat, times,
|
unittest2, json, os, tables, strutils, sets,
|
||||||
options,
|
options,
|
||||||
eth/[common, rlp], eth/trie/[db, trie_defs],
|
eth/[common, rlp], eth/trie/[db, trie_defs],
|
||||||
ethash, stew/endians2, nimcrypto,
|
stew/endians2, nimcrypto,
|
||||||
./test_helpers, ./test_allowed_to_fail,
|
./test_helpers, ./test_allowed_to_fail,
|
||||||
../premix/parser, test_config,
|
../premix/parser, test_config,
|
||||||
../nimbus/[vm_state, utils, vm_types, errors, transaction, constants, vm_types2],
|
../nimbus/[vm_state, utils, vm_types, errors, transaction, constants, vm_types2],
|
||||||
../nimbus/db/[db_chain, accounts_cache],
|
../nimbus/db/[db_chain, accounts_cache],
|
||||||
../nimbus/utils/header,
|
../nimbus/utils/header,
|
||||||
../nimbus/p2p/[executor, dao],
|
../nimbus/p2p/[executor, validate],
|
||||||
../nimbus/[config, chain_config],
|
../nimbus/chain_config,
|
||||||
../stateless/[tree_from_witness, witness_types]
|
../stateless/[tree_from_witness, witness_types]
|
||||||
|
|
||||||
type
|
type
|
||||||
@ -40,21 +40,6 @@ type
|
|||||||
debugData : JsonNode
|
debugData : JsonNode
|
||||||
network : string
|
network : string
|
||||||
|
|
||||||
MiningHeader = object
|
|
||||||
parentHash : Hash256
|
|
||||||
ommersHash : Hash256
|
|
||||||
coinbase : EthAddress
|
|
||||||
stateRoot : Hash256
|
|
||||||
txRoot : Hash256
|
|
||||||
receiptRoot : Hash256
|
|
||||||
bloom : common.BloomFilter
|
|
||||||
difficulty : DifficultyInt
|
|
||||||
blockNumber : BlockNumber
|
|
||||||
gasLimit : GasInt
|
|
||||||
gasUsed : GasInt
|
|
||||||
timestamp : EthTime
|
|
||||||
extraData : Blob
|
|
||||||
|
|
||||||
proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = false, trace = false)
|
proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = false, trace = false)
|
||||||
|
|
||||||
func normalizeNumber(n: JsonNode): JsonNode =
|
func normalizeNumber(n: JsonNode): JsonNode =
|
||||||
@ -229,211 +214,6 @@ proc blockWitness(vmState: BaseVMState, chainDB: BaseChainDB) =
|
|||||||
func validateBlockUnchanged(a, b: EthBlock): bool =
|
func validateBlockUnchanged(a, b: EthBlock): bool =
|
||||||
result = rlp.encode(a) == rlp.encode(b)
|
result = rlp.encode(a) == rlp.encode(b)
|
||||||
|
|
||||||
type Hash512 = MDigest[512]
|
|
||||||
var cacheByEpoch = initOrderedTable[uint64, seq[Hash512]]()
|
|
||||||
const CACHE_MAX_ITEMS = 10
|
|
||||||
|
|
||||||
proc mkCacheBytes(blockNumber: uint64): seq[Hash512] =
|
|
||||||
mkcache(getCacheSize(blockNumber), getSeedhash(blockNumber))
|
|
||||||
|
|
||||||
proc getCache(blockNumber: uint64): seq[Hash512] =
|
|
||||||
# TODO: this is very inefficient
|
|
||||||
let epochIndex = blockNumber div EPOCH_LENGTH
|
|
||||||
|
|
||||||
# Get the cache if already generated, marking it as recently used
|
|
||||||
if epochIndex in cacheByEpoch:
|
|
||||||
let c = cacheByEpoch[epochIndex]
|
|
||||||
cacheByEpoch.del(epochIndex) # pop and append at end
|
|
||||||
cacheByEpoch[epochIndex] = c
|
|
||||||
return c
|
|
||||||
|
|
||||||
# Generate the cache if it was not already in memory
|
|
||||||
# Simulate requesting mkcache by block number: multiply index by epoch length
|
|
||||||
let c = mkCacheBytes(epochIndex * EPOCH_LENGTH)
|
|
||||||
cacheByEpoch[epochIndex] = c
|
|
||||||
|
|
||||||
# Limit memory usage for cache
|
|
||||||
if cacheByEpoch.len > CACHE_MAX_ITEMS:
|
|
||||||
cacheByEpoch.del(epochIndex)
|
|
||||||
|
|
||||||
shallowCopy(result, c)
|
|
||||||
|
|
||||||
func cacheHash(x: openArray[Hash512]): Hash256 =
|
|
||||||
var ctx: keccak256
|
|
||||||
ctx.init()
|
|
||||||
|
|
||||||
for a in x:
|
|
||||||
ctx.update(a.data[0].unsafeAddr, uint(a.data.len))
|
|
||||||
|
|
||||||
ctx.finish result.data
|
|
||||||
ctx.clear()
|
|
||||||
|
|
||||||
proc checkPOW(blockNumber: Uint256, miningHash, mixHash: Hash256, nonce: BlockNonce, difficulty: DifficultyInt) =
|
|
||||||
let blockNumber = blockNumber.truncate(uint64)
|
|
||||||
let cache = blockNumber.getCache()
|
|
||||||
|
|
||||||
let size = getDataSize(blockNumber)
|
|
||||||
let miningOutput = hashimotoLight(size, cache, miningHash, uint64.fromBytesBE(nonce))
|
|
||||||
if miningOutput.mixDigest != mixHash:
|
|
||||||
echo "actual: ", miningOutput.mixDigest
|
|
||||||
echo "expected: ", mixHash
|
|
||||||
echo "blockNumber: ", blockNumber
|
|
||||||
echo "miningHash: ", miningHash
|
|
||||||
echo "nonce: ", nonce.toHex
|
|
||||||
echo "difficulty: ", difficulty
|
|
||||||
echo "size: ", size
|
|
||||||
echo "cache hash: ", cacheHash(cache)
|
|
||||||
raise newException(ValidationError, "mixHash mismatch")
|
|
||||||
|
|
||||||
let value = Uint256.fromBytesBE(miningOutput.value.data)
|
|
||||||
if value > Uint256.high div difficulty:
|
|
||||||
raise newException(ValidationError, "mining difficulty error")
|
|
||||||
|
|
||||||
func toMiningHeader(header: BlockHeader): MiningHeader =
|
|
||||||
result.parentHash = header.parentHash
|
|
||||||
result.ommersHash = header.ommersHash
|
|
||||||
result.coinbase = header.coinbase
|
|
||||||
result.stateRoot = header.stateRoot
|
|
||||||
result.txRoot = header.txRoot
|
|
||||||
result.receiptRoot = header.receiptRoot
|
|
||||||
result.bloom = header.bloom
|
|
||||||
result.difficulty = header.difficulty
|
|
||||||
result.blockNumber = header.blockNumber
|
|
||||||
result.gasLimit = header.gasLimit
|
|
||||||
result.gasUsed = header.gasUsed
|
|
||||||
result.timestamp = header.timestamp
|
|
||||||
result.extraData = header.extraData
|
|
||||||
|
|
||||||
func hash(header: MiningHeader): Hash256 =
|
|
||||||
keccakHash(rlp.encode(header))
|
|
||||||
|
|
||||||
proc validateSeal(header: BlockHeader) =
|
|
||||||
let miningHeader = header.toMiningHeader
|
|
||||||
let miningHash = miningHeader.hash
|
|
||||||
|
|
||||||
checkPOW(header.blockNumber, miningHash,
|
|
||||||
header.mixDigest, header.nonce, header.difficulty)
|
|
||||||
|
|
||||||
func validateGasLimit(gasLimit, parentGasLimit: GasInt) =
|
|
||||||
if gasLimit < GAS_LIMIT_MINIMUM:
|
|
||||||
raise newException(ValidationError, "Gas limit is below minimum")
|
|
||||||
if gasLimit > GAS_LIMIT_MAXIMUM:
|
|
||||||
raise newException(ValidationError, "Gas limit is above maximum")
|
|
||||||
let diff = gasLimit - parentGasLimit
|
|
||||||
if diff > (parentGasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR):
|
|
||||||
raise newException(ValidationError, "Gas limit difference to parent is too big")
|
|
||||||
|
|
||||||
proc validateHeader(header, parentHeader: BlockHeader, checkSeal: bool) =
|
|
||||||
if header.extraData.len > 32:
|
|
||||||
raise newException(ValidationError, "BlockHeader.extraData larger than 32 bytes")
|
|
||||||
|
|
||||||
validateGasLimit(header.gasLimit, parentHeader.gasLimit)
|
|
||||||
|
|
||||||
if header.blockNumber != parentHeader.blockNumber + 1:
|
|
||||||
raise newException(ValidationError, "Blocks must be numbered consecutively.")
|
|
||||||
|
|
||||||
if header.timestamp.toUnix <= parentHeader.timestamp.toUnix:
|
|
||||||
raise newException(ValidationError, "timestamp must be strictly later than parent")
|
|
||||||
|
|
||||||
if checkSeal:
|
|
||||||
validateSeal(header)
|
|
||||||
|
|
||||||
func validateUncle(currBlock, uncle, uncleParent: BlockHeader) =
|
|
||||||
if uncle.blockNumber >= currBlock.blockNumber:
|
|
||||||
raise newException(ValidationError, "uncle block number larger than current block number")
|
|
||||||
|
|
||||||
if uncle.blockNumber != uncleParent.blockNumber + 1:
|
|
||||||
raise newException(ValidationError, "Uncle number is not one above ancestor's number")
|
|
||||||
|
|
||||||
if uncle.timestamp.toUnix < uncleParent.timestamp.toUnix:
|
|
||||||
raise newException(ValidationError, "Uncle timestamp is before ancestor's timestamp")
|
|
||||||
|
|
||||||
if uncle.gasUsed > uncle.gasLimit:
|
|
||||||
raise newException(ValidationError, "Uncle's gas usage is above the limit")
|
|
||||||
|
|
||||||
proc validateGasLimit(chainDB: BaseChainDB, header: BlockHeader) =
|
|
||||||
let parentHeader = chainDB.getBlockHeader(header.parentHash)
|
|
||||||
let (lowBound, highBound) = gasLimitBounds(parentHeader)
|
|
||||||
|
|
||||||
if header.gasLimit < lowBound:
|
|
||||||
raise newException(ValidationError, "The gas limit is too low")
|
|
||||||
elif header.gasLimit > highBound:
|
|
||||||
raise newException(ValidationError, "The gas limit is too high")
|
|
||||||
|
|
||||||
proc validateUncles(chainDB: BaseChainDB, currBlock: EthBlock, checkSeal: bool) =
|
|
||||||
let hasUncles = currBlock.uncles.len > 0
|
|
||||||
let shouldHaveUncles = currBlock.header.ommersHash != EMPTY_UNCLE_HASH
|
|
||||||
|
|
||||||
if not hasUncles and not shouldHaveUncles:
|
|
||||||
# optimization to avoid loading ancestors from DB, since the block has no uncles
|
|
||||||
return
|
|
||||||
elif hasUncles and not shouldHaveUncles:
|
|
||||||
raise newException(ValidationError, "Block has uncles but header suggests uncles should be empty")
|
|
||||||
elif shouldHaveUncles and not hasUncles:
|
|
||||||
raise newException(ValidationError, "Header suggests block should have uncles but block has none")
|
|
||||||
|
|
||||||
# Check for duplicates
|
|
||||||
var uncleSet = initHashSet[Hash256]()
|
|
||||||
for uncle in currBlock.uncles:
|
|
||||||
let uncleHash = uncle.hash
|
|
||||||
if uncleHash in uncleSet:
|
|
||||||
raise newException(ValidationError, "Block contains duplicate uncles")
|
|
||||||
else:
|
|
||||||
uncleSet.incl uncleHash
|
|
||||||
|
|
||||||
let recentAncestorHashes = chainDB.getAncestorsHashes(MAX_UNCLE_DEPTH + 1, currBlock.header)
|
|
||||||
let recentUncleHashes = chainDB.getUncleHashes(recentAncestorHashes)
|
|
||||||
let blockHash =currBlock.header.hash
|
|
||||||
|
|
||||||
for uncle in currBlock.uncles:
|
|
||||||
let uncleHash = uncle.hash
|
|
||||||
|
|
||||||
if uncleHash == blockHash:
|
|
||||||
raise newException(ValidationError, "Uncle has same hash as block")
|
|
||||||
|
|
||||||
# ensure the uncle has not already been included.
|
|
||||||
if uncleHash in recentUncleHashes:
|
|
||||||
raise newException(ValidationError, "Duplicate uncle")
|
|
||||||
|
|
||||||
# ensure that the uncle is not one of the canonical chain blocks.
|
|
||||||
if uncleHash in recentAncestorHashes:
|
|
||||||
raise newException(ValidationError, "Uncle cannot be an ancestor")
|
|
||||||
|
|
||||||
# ensure that the uncle was built off of one of the canonical chain
|
|
||||||
# blocks.
|
|
||||||
if (uncle.parentHash notin recentAncestorHashes) or
|
|
||||||
(uncle.parentHash == currBlock.header.parentHash):
|
|
||||||
raise newException(ValidationError, "Uncle's parent is not an ancestor")
|
|
||||||
|
|
||||||
# Now perform VM level validation of the uncle
|
|
||||||
if checkSeal:
|
|
||||||
validateSeal(uncle)
|
|
||||||
|
|
||||||
let uncleParent = chainDB.getBlockHeader(uncle.parentHash)
|
|
||||||
validateUncle(currBlock.header, uncle, uncleParent)
|
|
||||||
|
|
||||||
func isGenesis(currBlock: EthBlock): bool =
|
|
||||||
result = currBlock.header.blockNumber == 0.u256 and currBlock.header.parentHash == GENESIS_PARENT_HASH
|
|
||||||
|
|
||||||
proc validateBlock(chainDB: BaseChainDB, currBlock: EthBlock, checkSeal: bool): bool =
|
|
||||||
if currBlock.isGenesis:
|
|
||||||
if currBlock.header.extraData.len > 32:
|
|
||||||
raise newException(ValidationError, "BlockHeader.extraData larger than 32 bytes")
|
|
||||||
return true
|
|
||||||
|
|
||||||
let parentHeader = chainDB.getBlockHeader(currBlock.header.parentHash)
|
|
||||||
validateHeader(currBlock.header, parentHeader, checkSeal)
|
|
||||||
|
|
||||||
if currBlock.uncles.len > MAX_UNCLES:
|
|
||||||
raise newException(ValidationError, "Number of uncles exceed limit.")
|
|
||||||
|
|
||||||
if not chainDB.exists(currBlock.header.stateRoot):
|
|
||||||
raise newException(ValidationError, "`state_root` was not found in the db.")
|
|
||||||
|
|
||||||
validateUncles(chainDB, currBlock, checkSeal)
|
|
||||||
validateGaslimit(chainDB, currBlock.header)
|
|
||||||
|
|
||||||
result = true
|
|
||||||
|
|
||||||
proc importBlock(tester: var Tester, chainDB: BaseChainDB,
|
proc importBlock(tester: var Tester, chainDB: BaseChainDB,
|
||||||
preminedBlock: EthBlock, tb: TestBlock, checkSeal, validation: bool): EthBlock =
|
preminedBlock: EthBlock, tb: TestBlock, checkSeal, validation: bool): EthBlock =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user