2021-05-18 11:18:15 +00:00
|
|
|
# Nimbus
|
2024-02-20 03:07:38 +00:00
|
|
|
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
2021-05-18 11:18:15 +00:00
|
|
|
# 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.
|
|
|
|
|
2023-12-12 19:12:56 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2021-05-18 11:18:15 +00:00
|
|
|
import
|
2024-08-19 07:42:07 +00:00
|
|
|
std/[sequtils, sets, strformat],
|
2023-12-12 19:12:56 +00:00
|
|
|
../db/ledger,
|
2024-10-26 07:19:48 +00:00
|
|
|
../common/common,
|
|
|
|
../transaction/call_types,
|
|
|
|
../transaction,
|
2023-10-05 03:04:12 +00:00
|
|
|
../utils/utils,
|
2022-12-13 20:06:26 +00:00
|
|
|
"."/[dao, eip4844, gaslimit, withdrawals],
|
2022-12-02 04:35:41 +00:00
|
|
|
./pow/[difficulty, header],
|
2023-10-05 03:04:12 +00:00
|
|
|
nimcrypto/utils as cryptoutils,
|
2024-05-30 12:54:03 +00:00
|
|
|
stew/objects,
|
|
|
|
results
|
2021-06-24 15:29:21 +00:00
|
|
|
|
|
|
|
from stew/byteutils
|
|
|
|
import nil
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
export
|
|
|
|
results
|
|
|
|
|
2021-06-24 15:29:21 +00:00
|
|
|
const
|
2022-01-24 13:08:33 +00:00
|
|
|
daoForkBlockExtraData* =
|
2021-06-24 15:29:21 +00:00
|
|
|
byteutils.hexToByteArray[13](DAOForkBlockExtra).toSeq
|
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
2024-06-16 03:22:06 +00:00
|
|
|
# Private validator functions
|
2021-05-19 10:40:03 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2023-09-18 20:20:28 +00:00
|
|
|
proc validateHeader(
|
|
|
|
com: CommonRef;
|
2024-10-16 01:34:12 +00:00
|
|
|
blk: Block;
|
|
|
|
parentHeader: Header;
|
2023-09-18 20:20:28 +00:00
|
|
|
checkSealOK: bool;
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
): Result[void,string] =
|
2024-10-16 01:34:12 +00:00
|
|
|
template header: Header = blk.header
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
# TODO this code is used for validating uncles also, though these get passed
|
|
|
|
# an empty body - avoid this by separating header and block validation
|
2021-10-12 04:06:39 +00:00
|
|
|
template inDAOExtraRange(blockNumber: BlockNumber): bool =
|
|
|
|
# EIP-799
|
|
|
|
# Blocks with block numbers in the range [1_920_000, 1_920_009]
|
|
|
|
# MUST have DAOForkBlockExtra
|
2022-12-02 04:35:41 +00:00
|
|
|
let daoForkBlock = com.daoForkBlock.get
|
2024-06-14 07:31:08 +00:00
|
|
|
let DAOHigh = daoForkBlock + DAOForkExtraRange
|
2022-12-02 04:35:41 +00:00
|
|
|
daoForkBlock <= blockNumber and
|
2021-10-12 04:06:39 +00:00
|
|
|
blockNumber < DAOHigh
|
|
|
|
|
2021-05-18 11:18:15 +00:00
|
|
|
if header.extraData.len > 32:
|
2024-10-16 01:34:12 +00:00
|
|
|
return err("Header.extraData larger than 32 bytes")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
if header.gasUsed == 0 and 0 < blk.transactions.len:
|
2023-06-24 13:56:44 +00:00
|
|
|
return err("zero gasUsed but transactions present");
|
2021-06-24 15:29:21 +00:00
|
|
|
|
2021-06-30 09:42:55 +00:00
|
|
|
if header.gasUsed < 0 or header.gasUsed > header.gasLimit:
|
|
|
|
return err("gasUsed should be non negative and smaller or equal gasLimit")
|
2021-09-28 15:16:15 +00:00
|
|
|
|
2024-06-14 07:31:08 +00:00
|
|
|
if header.number != parentHeader.number + 1:
|
2021-06-24 15:29:21 +00:00
|
|
|
return err("Blocks must be numbered consecutively")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2023-10-18 02:16:11 +00:00
|
|
|
if header.timestamp <= parentHeader.timestamp:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("timestamp must be strictly later than parent")
|
|
|
|
|
2024-07-07 06:52:11 +00:00
|
|
|
if header.gasLimit > GAS_LIMIT_MAXIMUM:
|
|
|
|
return err("gasLimit exceeds GAS_LIMIT_MAXIMUM")
|
2024-07-26 05:32:01 +00:00
|
|
|
|
2024-06-14 07:31:08 +00:00
|
|
|
if com.daoForkSupport and inDAOExtraRange(header.number):
|
2021-10-12 04:06:39 +00:00
|
|
|
if header.extraData != daoForkBlockExtraData:
|
|
|
|
return err("header extra data should be marked DAO")
|
2021-06-24 15:29:21 +00:00
|
|
|
|
2024-10-08 02:37:36 +00:00
|
|
|
if com.proofOfStake(header):
|
2022-02-08 09:50:22 +00:00
|
|
|
# EIP-4399 and EIP-3675
|
2024-06-14 07:31:08 +00:00
|
|
|
# no need to check mixHash because EIP-4399 override this field
|
2022-02-08 09:50:22 +00:00
|
|
|
# checking rule
|
2021-06-24 15:29:21 +00:00
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
if not header.difficulty.isZero:
|
|
|
|
return err("Non-zero difficulty in a post-merge block")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
if not header.nonce.isZeroMemory:
|
|
|
|
return err("Non-zero nonce in a post-merge block")
|
|
|
|
|
|
|
|
if header.ommersHash != EMPTY_UNCLE_HASH:
|
|
|
|
return err("Invalid ommers hash in a post-merge block")
|
|
|
|
else:
|
2022-12-02 04:35:41 +00:00
|
|
|
let calcDiffc = com.calcDifficulty(header.timestamp, parentHeader)
|
2021-10-05 23:31:35 +00:00
|
|
|
if header.difficulty < calcDiffc:
|
|
|
|
return err("provided header difficulty is too low")
|
|
|
|
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
? com.validateWithdrawals(header, blk.withdrawals)
|
|
|
|
? com.validateEip4844Header(header, parentHeader, blk.transactions)
|
2023-10-05 03:04:12 +00:00
|
|
|
? com.validateGasLimitOrBaseFee(header, parentHeader)
|
2022-12-13 20:06:26 +00:00
|
|
|
|
|
|
|
ok()
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2024-10-16 01:34:12 +00:00
|
|
|
proc validateUncles(com: CommonRef; header: Header;
|
|
|
|
uncles: openArray[Header];
|
2023-10-05 03:04:12 +00:00
|
|
|
checkSealOK: bool): Result[void,string]
|
|
|
|
{.gcsafe, raises: [].} =
|
2021-05-19 10:40:03 +00:00
|
|
|
let hasUncles = uncles.len > 0
|
|
|
|
let shouldHaveUncles = header.ommersHash != EMPTY_UNCLE_HASH
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
if not hasUncles and not shouldHaveUncles:
|
|
|
|
# optimization to avoid loading ancestors from DB, since the block has
|
|
|
|
# no uncles
|
2021-05-19 10:40:03 +00:00
|
|
|
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")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
# Check for duplicates
|
2024-10-16 01:34:12 +00:00
|
|
|
var uncleSet = HashSet[Hash32]()
|
2021-05-19 10:40:03 +00:00
|
|
|
for uncle in uncles:
|
2021-07-30 14:06:51 +00:00
|
|
|
let uncleHash = uncle.blockHash
|
2021-05-18 11:18:15 +00:00
|
|
|
if uncleHash in uncleSet:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("Block contains duplicate uncles")
|
2021-05-18 11:18:15 +00:00
|
|
|
else:
|
|
|
|
uncleSet.incl uncleHash
|
|
|
|
|
2024-11-07 01:24:21 +00:00
|
|
|
let
|
|
|
|
chainDB = com.db
|
|
|
|
recentAncestorHashes = ?chainDB.getAncestorsHashes(MAX_UNCLE_DEPTH + 1, header)
|
|
|
|
recentUncleHashes = ?chainDB.getUncleHashes(recentAncestorHashes)
|
|
|
|
blockHash = header.blockHash
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
for uncle in uncles:
|
2021-07-30 14:06:51 +00:00
|
|
|
let uncleHash = uncle.blockHash
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
if uncleHash == blockHash:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("Uncle has same hash as block")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
# ensure the uncle has not already been included.
|
|
|
|
if uncleHash in recentUncleHashes:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("Duplicate uncle")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
# ensure that the uncle is not one of the canonical chain blocks.
|
|
|
|
if uncleHash in recentAncestorHashes:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("Uncle cannot be an ancestor")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
|
|
|
# ensure that the uncle was built off of one of the canonical chain
|
|
|
|
# blocks.
|
|
|
|
if (uncle.parentHash notin recentAncestorHashes) or
|
2021-05-19 10:40:03 +00:00
|
|
|
(uncle.parentHash == header.parentHash):
|
|
|
|
return err("Uncle's parent is not an ancestor")
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2024-06-14 07:31:08 +00:00
|
|
|
if uncle.number >= header.number:
|
2023-10-05 03:04:12 +00:00
|
|
|
return err("uncle block number larger than current block number")
|
|
|
|
|
2021-06-18 07:37:59 +00:00
|
|
|
# check uncle against own parent
|
2024-11-07 01:24:21 +00:00
|
|
|
let parent = ?chainDB.getBlockHeader(uncle.parentHash)
|
2021-06-18 07:37:59 +00:00
|
|
|
if uncle.timestamp <= parent.timestamp:
|
|
|
|
return err("Uncle's parent must me older")
|
|
|
|
|
2024-11-07 01:24:21 +00:00
|
|
|
let uncleParent = ?chainDB.getBlockHeader(uncle.parentHash)
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
? com.validateHeader(
|
2024-10-16 01:34:12 +00:00
|
|
|
Block.init(uncle, BlockBody()), uncleParent, checkSealOK)
|
2021-06-30 13:30:39 +00:00
|
|
|
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
ok()
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public function, extracted from executor
|
|
|
|
# ------------------------------------------------------------------------------
|
2021-05-18 11:18:15 +00:00
|
|
|
|
2024-10-26 07:19:48 +00:00
|
|
|
proc validateLegacySignatureForm(tx: Transaction, fork: EVMFork): bool =
|
|
|
|
var
|
|
|
|
vMin = 27'u64
|
|
|
|
vMax = 28'u64
|
|
|
|
|
|
|
|
if tx.V >= EIP155_CHAIN_ID_OFFSET:
|
|
|
|
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
|
|
|
|
vMin = 35 + (2 * chainId)
|
|
|
|
vMax = vMin + 1
|
|
|
|
|
|
|
|
var isValid = tx.R >= UInt256.one
|
|
|
|
isValid = isValid and tx.S >= UInt256.one
|
|
|
|
isValid = isValid and tx.V >= vMin
|
|
|
|
isValid = isValid and tx.V <= vMax
|
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
|
|
|
|
if fork >= FkHomestead:
|
|
|
|
isValid = isValid and tx.S < SECPK1_N div 2
|
|
|
|
|
|
|
|
isValid
|
|
|
|
|
|
|
|
proc validateEip2930SignatureForm(tx: Transaction): bool =
|
|
|
|
var isValid = tx.V == 0'u64 or tx.V == 1'u64
|
|
|
|
isValid = isValid and tx.S >= UInt256.one
|
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
isValid
|
|
|
|
|
2023-08-21 02:10:18 +00:00
|
|
|
func gasCost*(tx: Transaction): UInt256 =
|
2023-06-24 13:56:44 +00:00
|
|
|
if tx.txType >= TxEip4844:
|
2024-06-14 07:31:08 +00:00
|
|
|
tx.gasLimit.u256 * tx.maxFeePerGas.u256 + tx.getTotalBlobGas.u256 * tx.maxFeePerBlobGas
|
2023-06-24 13:56:44 +00:00
|
|
|
elif tx.txType >= TxEip1559:
|
2024-06-14 07:31:08 +00:00
|
|
|
tx.gasLimit.u256 * tx.maxFeePerGas.u256
|
2023-06-24 13:56:44 +00:00
|
|
|
else:
|
|
|
|
tx.gasLimit.u256 * tx.gasPrice.u256
|
|
|
|
|
2023-08-21 02:10:18 +00:00
|
|
|
proc validateTxBasic*(
|
2022-01-10 09:04:06 +00:00
|
|
|
tx: Transaction; ## tx to validate
|
2023-10-23 13:59:57 +00:00
|
|
|
fork: EVMFork,
|
|
|
|
validateFork: bool = true): Result[void, string] =
|
|
|
|
|
|
|
|
if validateFork:
|
|
|
|
if tx.txType == TxEip2930 and fork < FkBerlin:
|
|
|
|
return err("invalid tx: Eip2930 Tx type detected before Berlin")
|
2024-02-20 03:07:38 +00:00
|
|
|
|
2023-10-23 13:59:57 +00:00
|
|
|
if tx.txType == TxEip1559 and fork < FkLondon:
|
|
|
|
return err("invalid tx: Eip1559 Tx type detected before London")
|
2024-02-20 03:07:38 +00:00
|
|
|
|
2023-10-23 13:59:57 +00:00
|
|
|
if tx.txType == TxEip4844 and fork < FkCancun:
|
|
|
|
return err("invalid tx: Eip4844 Tx type detected before Cancun")
|
2023-06-24 13:56:44 +00:00
|
|
|
|
2024-11-25 10:28:03 +00:00
|
|
|
if tx.txType == TxEip7702 and fork < FkPrague:
|
|
|
|
return err("invalid tx: Eip7702 Tx type detected before Prague")
|
|
|
|
|
2023-01-10 04:25:23 +00:00
|
|
|
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
|
2023-06-12 05:01:48 +00:00
|
|
|
return err("invalid tx: initcode size exceeds maximum")
|
2023-01-04 13:11:33 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
# The total must be the larger of the two
|
|
|
|
if tx.maxFeePerGasNorm < tx.maxPriorityFeePerGasNorm:
|
|
|
|
return err(&"invalid tx: maxFee is smaller than maPriorityFee. maxFee={tx.maxFeePerGas}, maxPriorityFee={tx.maxPriorityFeePerGasNorm}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
if tx.gasLimit < tx.intrinsicGas(fork):
|
|
|
|
return err(&"invalid tx: not enough gas to perform calculation. avail={tx.gasLimit}, require={tx.intrinsicGas(fork)}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
if fork >= FkCancun:
|
|
|
|
if tx.payload.len > MAX_CALLDATA_SIZE:
|
|
|
|
return err(&"invalid tx: payload len exceeds MAX_CALLDATA_SIZE. len={tx.payload.len}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
if tx.accessList.len > MAX_ACCESS_LIST_SIZE:
|
|
|
|
return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" &
|
|
|
|
$tx.accessList.len)
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
for i, acl in tx.accessList:
|
|
|
|
if acl.storageKeys.len > MAX_ACCESS_LIST_STORAGE_KEYS:
|
|
|
|
return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " &
|
|
|
|
&"index={i}, len={acl.storageKeys.len}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-10-26 07:19:48 +00:00
|
|
|
if tx.txType == TxLegacy:
|
|
|
|
if not validateLegacySignatureForm(tx, fork):
|
|
|
|
return err("invalid tx: invalid legacy signature form")
|
|
|
|
else:
|
|
|
|
if not validateEip2930SignatureForm(tx):
|
|
|
|
return err("invalid tx: invalid post EIP-2930 signature form")
|
|
|
|
|
2024-11-25 10:28:03 +00:00
|
|
|
if tx.txType == TxEip4844:
|
2024-08-19 07:42:07 +00:00
|
|
|
if tx.to.isNone:
|
|
|
|
return err("invalid tx: destination must be not empty")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
if tx.versionedHashes.len == 0:
|
|
|
|
return err("invalid tx: there must be at least one blob")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
if tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
|
|
|
|
return err(&"invalid tx: versioned hashes len exceeds MAX_BLOBS_PER_BLOCK={MAX_BLOBS_PER_BLOCK}. get={tx.versionedHashes.len}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
for i, bv in tx.versionedHashes:
|
|
|
|
if bv.data[0] != VERSIONED_HASH_VERSION_KZG:
|
|
|
|
return err("invalid tx: one of blobVersionedHash has invalid version. " &
|
|
|
|
&"get={bv.data[0].int}, expect={VERSIONED_HASH_VERSION_KZG.int}")
|
2023-08-21 02:10:18 +00:00
|
|
|
|
2024-11-25 10:28:03 +00:00
|
|
|
if tx.txType == TxEip7702:
|
|
|
|
if tx.authorizationList.len == 0:
|
|
|
|
return err("invalid tx: authorization list must not empty")
|
|
|
|
|
|
|
|
const SECP256K1halfN = SECPK1_N div 2
|
|
|
|
|
|
|
|
for auth in tx.authorizationList:
|
|
|
|
if auth.v > 1'u64:
|
|
|
|
return err("invalid tx: auth.v must be 0 or 1")
|
|
|
|
|
|
|
|
if auth.s > SECP256K1halfN:
|
|
|
|
return err("invalid tx: auth.s must be <= SECP256K1N/2")
|
|
|
|
|
2023-08-21 02:10:18 +00:00
|
|
|
ok()
|
|
|
|
|
|
|
|
proc validateTransaction*(
|
|
|
|
roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction
|
|
|
|
tx: Transaction; ## tx to validate
|
2024-10-26 07:19:48 +00:00
|
|
|
sender: Address; ## tx.recoverSender
|
2023-08-21 02:10:18 +00:00
|
|
|
maxLimit: GasInt; ## gasLimit from block header
|
|
|
|
baseFee: UInt256; ## baseFee from block header
|
2024-10-26 07:19:48 +00:00
|
|
|
excessBlobGas: uint64; ## excessBlobGas from parent block header
|
2023-08-21 02:10:18 +00:00
|
|
|
fork: EVMFork): Result[void, string] =
|
|
|
|
|
2024-08-19 07:42:07 +00:00
|
|
|
? validateTxBasic(tx, fork)
|
2023-08-21 02:10:18 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
balance = roDB.getBalance(sender)
|
|
|
|
nonce = roDB.getNonce(sender)
|
|
|
|
|
2022-01-10 09:04:06 +00:00
|
|
|
# Note that the following check bears some plausibility but is _not_
|
|
|
|
# covered by the eip-1559 reference (sort of) pseudo code, for details
|
|
|
|
# see `https://eips.ethereum.org/EIPS/eip-1559#specification`_
|
|
|
|
#
|
|
|
|
# Rather this check is needed for surviving the post-London unit test
|
|
|
|
# eth_tests/GeneralStateTests/stEIP1559/lowGasLimit.json which seems to
|
|
|
|
# be sourced and generated from
|
|
|
|
# eth_tests/src/GeneralStateTestsFiller/stEIP1559/lowGasLimitFiller.yml
|
|
|
|
#
|
|
|
|
# Interestingly, the hive tests do not use this particular test but rather
|
|
|
|
# eth_tests/BlockchainTests/GeneralStateTests/stEIP1559/lowGasLimit.json
|
|
|
|
# from a parallel tests series which look like somehow expanded versions.
|
|
|
|
#
|
|
|
|
# The parallel lowGasLimit.json test never triggers the case checked below
|
|
|
|
# as the paricular transaction is omitted (the txs list is just set empty.)
|
2024-08-19 07:42:07 +00:00
|
|
|
if maxLimit < tx.gasLimit:
|
|
|
|
return err(&"invalid tx: block header gasLimit exceeded. maxLimit={maxLimit}, gasLimit={tx.gasLimit}")
|
|
|
|
|
|
|
|
# ensure that the user was willing to at least pay the base fee
|
|
|
|
if tx.maxFeePerGasNorm < baseFee.truncate(GasInt):
|
|
|
|
return err(&"invalid tx: maxFee is smaller than baseFee. maxFee={tx.maxFeePerGas}, baseFee={baseFee}")
|
|
|
|
|
|
|
|
# the signer must be able to fully afford the transaction
|
|
|
|
let gasCost = tx.gasCost()
|
|
|
|
|
|
|
|
if balance < gasCost:
|
|
|
|
return err(&"invalid tx: not enough cash for gas. avail={balance}, require={gasCost}")
|
|
|
|
|
|
|
|
if balance - gasCost < tx.value:
|
|
|
|
return err(&"invalid tx: not enough cash to send. avail={balance}, availMinusGas={balance-gasCost}, require={tx.value}")
|
|
|
|
|
|
|
|
if tx.nonce != nonce:
|
|
|
|
return err(&"invalid tx: account nonce mismatch. txNonce={tx.nonce}, accNonce={nonce}")
|
|
|
|
|
|
|
|
if tx.nonce == high(uint64):
|
|
|
|
return err(&"invalid tx: nonce at maximum")
|
|
|
|
|
|
|
|
# EIP-3607 Reject transactions from senders with deployed code
|
|
|
|
# The EIP spec claims this attack never happened before
|
|
|
|
# Clients might choose to disable this rule for RPC calls like
|
|
|
|
# `eth_call` and `eth_estimateGas`
|
|
|
|
# EOA = Externally Owned Account
|
|
|
|
let codeHash = roDB.getCodeHash(sender)
|
|
|
|
if codeHash != EMPTY_CODE_HASH:
|
|
|
|
return err(&"invalid tx: sender is not an EOA. sender={sender.toHex}, codeHash={codeHash.data.toHex}")
|
|
|
|
|
2024-11-25 10:28:03 +00:00
|
|
|
if tx.txType == TxEip4844:
|
2024-08-19 07:42:07 +00:00
|
|
|
# ensure that the user was willing to at least pay the current data gasprice
|
|
|
|
let blobGasPrice = getBlobBaseFee(excessBlobGas)
|
|
|
|
if tx.maxFeePerBlobGas < blobGasPrice:
|
|
|
|
return err("invalid tx: maxFeePerBlobGas smaller than blobGasPrice. " &
|
|
|
|
&"maxFeePerBlobGas={tx.maxFeePerBlobGas}, blobGasPrice={blobGasPrice}")
|
2023-06-12 05:01:48 +00:00
|
|
|
|
|
|
|
ok()
|
2022-01-10 09:04:06 +00:00
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions, extracted from test_blockchain_json
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
proc validateHeaderAndKinship*(
|
2022-12-02 04:35:41 +00:00
|
|
|
com: CommonRef;
|
2024-10-16 01:34:12 +00:00
|
|
|
blk: Block;
|
|
|
|
parent: Header;
|
2023-09-18 20:20:28 +00:00
|
|
|
checkSealOK: bool;
|
|
|
|
): Result[void, string]
|
2023-10-05 03:04:12 +00:00
|
|
|
{.gcsafe, raises: [].} =
|
2024-10-16 01:34:12 +00:00
|
|
|
template header: Header = blk.header
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
|
2021-05-19 10:40:03 +00:00
|
|
|
if header.isGenesis:
|
|
|
|
if header.extraData.len > 32:
|
2024-10-16 01:34:12 +00:00
|
|
|
return err("Header.extraData larger than 32 bytes")
|
2021-05-19 10:40:03 +00:00
|
|
|
return ok()
|
|
|
|
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
? com.validateHeader(blk, parent, checkSealOK)
|
2021-05-19 10:40:03 +00:00
|
|
|
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
if blk.uncles.len > MAX_UNCLES:
|
2021-05-19 10:40:03 +00:00
|
|
|
return err("Number of uncles exceed limit.")
|
|
|
|
|
2024-10-08 02:37:36 +00:00
|
|
|
if not com.proofOfStake(header):
|
Consolidate block type for block processing (#2325)
This PR consolidates the split header-body sequences into a single EthBlock
sequence and cleans up the fallout from that which significantly reduces
block processing overhead during import thanks to less garbage collection
and fewer copies of things all around.
Notably, since the number of headers must always match the number of bodies,
we also get rid of a pointless degree of freedom that in the future could
introduce unnecessary bugs.
* only read header and body from era file
* avoid several unnecessary copies along the block processing way
* simplify signatures, cleaning up unused arguemnts and returns
* use `stew/assign2` in a few strategic places where the generated
nim assignent is slow and add a few `move` to work around poor
analysis in nim 1.6 (will need to be revisited for 2.0)
```
stats-20240607_2223-a814aa0b.csv vs stats-20240608_0714-21c1d0a9.csv
bps_x bps_y tps_x tps_y bpsd tpsd timed
block_number
(498305, 713245] 1,540.52 1,809.73 2,361.58 2775.340189 17.63% 17.63% -14.92%
(713245, 928185] 730.36 865.26 1,715.90 2028.973852 18.01% 18.01% -15.21%
(928185, 1143126] 663.03 789.10 2,529.26 3032.490771 19.79% 19.79% -16.28%
(1143126, 1358066] 393.46 508.05 2,152.50 2777.578119 29.13% 29.13% -22.50%
(1358066, 1573007] 370.88 440.72 2,351.31 2791.896052 18.81% 18.81% -15.80%
(1573007, 1787947] 283.65 335.11 2,068.93 2441.373402 17.60% 17.60% -14.91%
(1787947, 2002888] 287.29 342.11 2,078.39 2474.179448 18.99% 18.99% -15.91%
(2002888, 2217828] 293.38 343.16 2,208.83 2584.77457 17.16% 17.16% -14.61%
(2217828, 2432769] 140.09 167.86 1,081.87 1296.336926 18.82% 18.82% -15.80%
blocks: 1934464, baseline: 3h13m1s, contender: 2h43m47s
bpsd (mean): 19.55%
tpsd (mean): 19.55%
Time (total): -29m13s, -15.14%
```
2024-06-09 14:32:20 +00:00
|
|
|
? com.validateUncles(header, blk.uncles, checkSealOK)
|
|
|
|
|
|
|
|
ok()
|
2021-10-05 23:31:35 +00:00
|
|
|
|
2021-05-18 11:18:15 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|