re-wrote validation with exceptionless functions
why: exceptions were from test code should be avoided in production code
This commit is contained in:
parent
40c7bdfc06
commit
d6a5cecb98
|
@ -11,7 +11,6 @@
|
|||
import
|
||||
../constants,
|
||||
../db/[db_chain, accounts_cache],
|
||||
../errors,
|
||||
../transaction,
|
||||
../utils,
|
||||
../utils/header,
|
||||
|
@ -19,17 +18,19 @@ import
|
|||
../vm_types,
|
||||
../vm_types2,
|
||||
chronicles,
|
||||
eth/[common, rlp],
|
||||
eth/trie/trie_defs,
|
||||
eth/[common, rlp, trie/trie_defs],
|
||||
ethash,
|
||||
nimcrypto,
|
||||
options,
|
||||
sets,
|
||||
stew/endians2,
|
||||
stew/[results, endians2],
|
||||
strutils,
|
||||
tables,
|
||||
times
|
||||
|
||||
export
|
||||
results
|
||||
|
||||
type
|
||||
MiningHeader = object
|
||||
parentHash : Hash256
|
||||
|
@ -54,7 +55,34 @@ const
|
|||
CACHE_MAX_ITEMS = 10
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# Private Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
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))
|
||||
|
||||
func isGenesis(header: BlockHeader): bool =
|
||||
header.blockNumber == 0.u256 and
|
||||
header.parentHash == GENESIS_PARENT_HASH
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private cache management functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc mkCacheBytes(blockNumber: uint64): seq[Hash512] =
|
||||
|
@ -100,67 +128,177 @@ func cacheHash(x: openArray[Hash512]): Hash256 =
|
|||
ctx.finish result.data
|
||||
ctx.clear()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Pivate validator functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc checkPOW(cacheByEpoch: CacheByEpoch;
|
||||
blockNumber: Uint256; miningHash, mixHash: Hash256,
|
||||
nonce: BlockNonce, difficulty: DifficultyInt) =
|
||||
let blockNumber = blockNumber.truncate(uint64)
|
||||
let cache = cacheByEpoch.getCache(blockNumber)
|
||||
proc checkPOW(blockNumber: Uint256; miningHash, mixHash: Hash256;
|
||||
nonce: BlockNonce; difficulty: DifficultyInt;
|
||||
cacheByEpoch: CacheByEpoch): Result[void,string] =
|
||||
let
|
||||
blockNumber = blockNumber.truncate(uint64)
|
||||
cache = cacheByEpoch.getCache(blockNumber)
|
||||
size = getDataSize(blockNumber)
|
||||
miningOutput = hashimotoLight(
|
||||
size, cache, miningHash, uint64.fromBytesBE(nonce))
|
||||
|
||||
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")
|
||||
debug "mixHash mismatch",
|
||||
actual = miningOutput.mixDigest,
|
||||
expected = mixHash,
|
||||
blockNumber = blockNumber,
|
||||
miningHash = miningHash,
|
||||
nonce = nonce.toHex,
|
||||
difficulty = difficulty,
|
||||
size = size,
|
||||
cachedHash = cacheHash(cache)
|
||||
return err("mixHash mismatch")
|
||||
|
||||
let value = Uint256.fromBytesBE(miningOutput.value.data)
|
||||
if value > Uint256.high div difficulty:
|
||||
raise newException(ValidationError, "mining difficulty error")
|
||||
return err("mining difficulty error")
|
||||
|
||||
result = ok()
|
||||
|
||||
|
||||
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(cacheByEpoch: CacheByEpoch; header: BlockHeader) =
|
||||
proc validateSeal(cacheByEpoch: CacheByEpoch;
|
||||
header: BlockHeader): Result[void,string] =
|
||||
let miningHeader = header.toMiningHeader
|
||||
let miningHash = miningHeader.hash
|
||||
|
||||
cacheByEpoch.checkPOW(header.blockNumber, miningHash,
|
||||
header.mixDigest, header.nonce, header.difficulty)
|
||||
checkPOW(header.blockNumber, miningHash,
|
||||
header.mixDigest, header.nonce, header.difficulty, cacheByEpoch)
|
||||
|
||||
|
||||
proc validateGasLimit(chainDB: BaseChainDB;
|
||||
header: BlockHeader): Result[void,string] =
|
||||
let parentHeader = chainDB.getBlockHeader(header.parentHash)
|
||||
let (lowBound, highBound) = gasLimitBounds(parentHeader)
|
||||
|
||||
if header.gasLimit < lowBound:
|
||||
return err("The gas limit is too low")
|
||||
if header.gasLimit > highBound:
|
||||
return err("The gas limit is too high")
|
||||
|
||||
result = ok()
|
||||
|
||||
|
||||
func validateGasLimit(gasLimit, parentGasLimit: GasInt): Result[void,string] =
|
||||
if gasLimit < GAS_LIMIT_MINIMUM:
|
||||
return err("Gas limit is below minimum")
|
||||
if gasLimit > GAS_LIMIT_MAXIMUM:
|
||||
return err("Gas limit is above maximum")
|
||||
|
||||
let diff = gasLimit - parentGasLimit
|
||||
if diff > (parentGasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR):
|
||||
return err("Gas limit difference to parent is too big")
|
||||
|
||||
result = ok()
|
||||
|
||||
proc validateHeader(header, parentHeader: BlockHeader; checkSealOK: bool;
|
||||
cacheByEpoch: CacheByEpoch): Result[void,string] =
|
||||
if header.extraData.len > 32:
|
||||
return err("BlockHeader.extraData larger than 32 bytes")
|
||||
|
||||
result = validateGasLimit(header.gasLimit, parentHeader.gasLimit)
|
||||
if result.isErr:
|
||||
return
|
||||
|
||||
if header.blockNumber != parentHeader.blockNumber + 1:
|
||||
return err("Blocks must be numbered consecutively.")
|
||||
|
||||
if header.timestamp.toUnix <= parentHeader.timestamp.toUnix:
|
||||
return err("timestamp must be strictly later than parent")
|
||||
|
||||
if checkSealOK:
|
||||
return cacheByEpoch.validateSeal(header)
|
||||
|
||||
result = ok()
|
||||
|
||||
|
||||
func validateUncle(currBlock, uncle, uncleParent: BlockHeader):
|
||||
Result[void,string] =
|
||||
if uncle.blockNumber >= currBlock.blockNumber:
|
||||
return err("uncle block number larger than current block number")
|
||||
|
||||
if uncle.blockNumber != uncleParent.blockNumber + 1:
|
||||
return err("Uncle number is not one above ancestor's number")
|
||||
|
||||
if uncle.timestamp.toUnix < uncleParent.timestamp.toUnix:
|
||||
return err("Uncle timestamp is before ancestor's timestamp")
|
||||
|
||||
if uncle.gasUsed > uncle.gasLimit:
|
||||
return err("Uncle's gas usage is above the limit")
|
||||
|
||||
result = ok()
|
||||
|
||||
|
||||
proc validateUncles(chainDB: BaseChainDB; header: BlockHeader;
|
||||
uncles: seq[BlockHeader]; checkSealOK: bool;
|
||||
cacheByEpoch: CacheByEpoch): Result[void,string] =
|
||||
let hasUncles = uncles.len > 0
|
||||
let shouldHaveUncles = 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 ok()
|
||||
if hasUncles and not shouldHaveUncles:
|
||||
return err("Block has uncles but header suggests uncles should be empty")
|
||||
if shouldHaveUncles and not hasUncles:
|
||||
return err("Header suggests block should have uncles but block has none")
|
||||
|
||||
# Check for duplicates
|
||||
var uncleSet = initHashSet[Hash256]()
|
||||
for uncle in uncles:
|
||||
let uncleHash = uncle.hash
|
||||
if uncleHash in uncleSet:
|
||||
return err("Block contains duplicate uncles")
|
||||
else:
|
||||
uncleSet.incl uncleHash
|
||||
|
||||
let recentAncestorHashes = chainDB.getAncestorsHashes(
|
||||
MAX_UNCLE_DEPTH + 1, header)
|
||||
let recentUncleHashes = chainDB.getUncleHashes(recentAncestorHashes)
|
||||
let blockHash = header.hash
|
||||
|
||||
for uncle in uncles:
|
||||
let uncleHash = uncle.hash
|
||||
|
||||
if uncleHash == blockHash:
|
||||
return err("Uncle has same hash as block")
|
||||
|
||||
# ensure the uncle has not already been included.
|
||||
if uncleHash in recentUncleHashes:
|
||||
return err("Duplicate uncle")
|
||||
|
||||
# ensure that the uncle is not one of the canonical chain blocks.
|
||||
if uncleHash in recentAncestorHashes:
|
||||
return err("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 == header.parentHash):
|
||||
return err("Uncle's parent is not an ancestor")
|
||||
|
||||
# Now perform VM level validation of the uncle
|
||||
if checkSealOK:
|
||||
result = cacheByEpoch.validateSeal(uncle)
|
||||
if result.isErr:
|
||||
return
|
||||
|
||||
let uncleParent = chainDB.getBlockHeader(uncle.parentHash)
|
||||
result = validateUncle(header, uncle, uncleParent)
|
||||
if result.isErr:
|
||||
return
|
||||
|
||||
result = ok()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Puplic function, extracted from executor
|
||||
# Public function, extracted from executor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newCacheByEpoch*(): CacheByEpoch =
|
||||
newOrderedTable[uint64, seq[Hash512]]()
|
||||
|
||||
|
||||
proc validateTransaction*(vmState: BaseVMState, tx: Transaction,
|
||||
sender: EthAddress, fork: Fork): bool =
|
||||
let balance = vmState.readOnlyStateDB.getBalance(sender)
|
||||
|
@ -201,156 +339,36 @@ proc validateTransaction*(vmState: BaseVMState, tx: Transaction,
|
|||
|
||||
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 newCacheByEpoch*(): CacheByEpoch =
|
||||
newOrderedTable[uint64, seq[Hash512]]()
|
||||
|
||||
|
||||
proc validateHeader*(header, parentHeader: BlockHeader;
|
||||
checkSeal: bool; cacheByEpoch: CacheByEpoch) =
|
||||
if header.extraData.len > 32:
|
||||
raise newException(
|
||||
ValidationError, "BlockHeader.extraData larger than 32 bytes")
|
||||
proc validateKinship*(chainDB: BaseChainDB; header: BlockHeader;
|
||||
uncles: seq[BlockHeader]; checkSealOK: bool;
|
||||
cacheByEpoch: CacheByEpoch): Result[void,string] =
|
||||
if header.isGenesis:
|
||||
if header.extraData.len > 32:
|
||||
return err("BlockHeader.extraData larger than 32 bytes")
|
||||
return ok()
|
||||
|
||||
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:
|
||||
cacheByEpoch.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; cacheByEpoch: CacheByEpoch) =
|
||||
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
|
||||
result = header.validateHeader(parentHeader, checkSealOK, cacheByEpoch)
|
||||
if result.isErr:
|
||||
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
|
||||
if uncles.len > MAX_UNCLES:
|
||||
return err("Number of uncles exceed limit.")
|
||||
|
||||
let recentAncestorHashes = chainDB.getAncestorsHashes(
|
||||
MAX_UNCLE_DEPTH + 1, currBlock.header)
|
||||
let recentUncleHashes = chainDB.getUncleHashes(recentAncestorHashes)
|
||||
let blockHash =currBlock.header.hash
|
||||
if not chainDB.exists(header.stateRoot):
|
||||
return err("`state_root` was not found in the db.")
|
||||
|
||||
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:
|
||||
cacheByEpoch.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; cacheByEpoch: CacheByEpoch): 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, cacheByEpoch)
|
||||
|
||||
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, cacheByEpoch)
|
||||
validateGaslimit(chainDB, currBlock.header)
|
||||
|
||||
result = true
|
||||
result = chainDB.validateUncles(header, uncles, checkSealOK, cacheByEpoch)
|
||||
if result.isOk:
|
||||
result = chainDB.validateGaslimit(header)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
|
|
|
@ -216,6 +216,13 @@ proc blockWitness(vmState: BaseVMState, chainDB: BaseChainDB) =
|
|||
func validateBlockUnchanged(a, b: EthBlock): bool =
|
||||
result = rlp.encode(a) == rlp.encode(b)
|
||||
|
||||
proc validateBlock(chainDB: BaseChainDB;
|
||||
ethBlock: EthBlock; checkSealOK: bool): bool =
|
||||
let rc = chainDB.validateKinship(
|
||||
ethBlock.header, ethBlock.uncles, checkSealOK, cacheByEpoch)
|
||||
if rc.isErr:
|
||||
debugEcho "invalid block: " & rc.error
|
||||
rc.isOk
|
||||
|
||||
proc importBlock(tester: var Tester, chainDB: BaseChainDB,
|
||||
preminedBlock: EthBlock, tb: TestBlock, checkSeal, validation: bool): EthBlock =
|
||||
|
@ -252,7 +259,7 @@ proc importBlock(tester: var Tester, chainDB: BaseChainDB,
|
|||
if validation:
|
||||
if not validateBlockUnchanged(result, preminedBlock):
|
||||
raise newException(ValidationError, "block changed")
|
||||
if not validateBlock(chainDB, result, checkSeal, cacheByEpoch):
|
||||
if not validateBlock(chainDB, result, checkSeal):
|
||||
raise newException(ValidationError, "invalid block")
|
||||
|
||||
discard chainDB.persistHeaderToDb(preminedBlock.header)
|
||||
|
@ -297,8 +304,7 @@ proc runTester(tester: var Tester, chainDB: BaseChainDB, testStatusIMPL: var Tes
|
|||
try:
|
||||
let (preminedBlock, _, _) = tester.applyFixtureBlockToChain(
|
||||
testBlock, chainDB, checkSeal, validation = false) # we manually validate below
|
||||
check validateBlock(
|
||||
chainDB, preminedBlock, checkSeal, cacheByEpoch) == true
|
||||
check validateBlock(chainDB, preminedBlock, checkSeal) == true
|
||||
except:
|
||||
debugEcho "FATAL ERROR(WE HAVE BUG): ", getCurrentExceptionMsg()
|
||||
|
||||
|
|
Loading…
Reference in New Issue