mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-13 05:44:40 +00:00
0ffc17d153
* Merge test_txpool and test_txpool2 * Fix copyright year
576 lines
15 KiB
Nim
576 lines
15 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018-2025 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
|
|
std/math,
|
|
eth/common/keys,
|
|
results,
|
|
unittest2,
|
|
../hive_integration/nodocker/engine/tx_sender,
|
|
../hive_integration/nodocker/engine/cancun/blobs,
|
|
../nimbus/db/ledger,
|
|
../nimbus/core/chain,
|
|
../nimbus/core/eip4844,
|
|
../nimbus/[config, transaction, constants],
|
|
../nimbus/core/tx_pool,
|
|
../nimbus/core/tx_pool/tx_desc,
|
|
../nimbus/core/casper,
|
|
../nimbus/common/common,
|
|
../nimbus/utils/utils,
|
|
./macro_assembler
|
|
|
|
const
|
|
genesisFile = "tests/customgenesis/merge.json"
|
|
feeRecipient = address"0000000000000000000000000000000000000212"
|
|
recipient = address"0000000000000000000000000000000000000213"
|
|
recipient214 = address"0000000000000000000000000000000000000214"
|
|
prevRandao = Bytes32 EMPTY_UNCLE_HASH
|
|
contractCode = evmByteCode:
|
|
PrevRandao # VAL
|
|
Push1 "0x11" # KEY
|
|
Sstore # OP
|
|
Stop
|
|
slot = 0x11.u256
|
|
amount = 1000.u256
|
|
|
|
type
|
|
TestEnv = object
|
|
conf : NimbusConf
|
|
com : CommonRef
|
|
chain : ForkedChainRef
|
|
xp : TxPoolRef
|
|
sender: TxSender
|
|
|
|
CustomTx = CustomTransactionData
|
|
|
|
proc initEnv(envFork: HardFork): TestEnv =
|
|
var conf = makeConfig(
|
|
@["--custom-network:" & genesisFile]
|
|
)
|
|
|
|
doAssert envFork >= MergeFork
|
|
|
|
if envFork >= MergeFork:
|
|
conf.networkParams.config.mergeNetsplitBlock = Opt.some(0'u64)
|
|
|
|
if envFork >= Shanghai:
|
|
conf.networkParams.config.shanghaiTime = Opt.some(0.EthTime)
|
|
|
|
if envFork >= Cancun:
|
|
conf.networkParams.config.cancunTime = Opt.some(0.EthTime)
|
|
|
|
if envFork >= Prague:
|
|
conf.networkParams.config.pragueTime = Opt.some(0.EthTime)
|
|
|
|
conf.networkParams.genesis.alloc[recipient] = GenesisAccount(code: contractCode)
|
|
|
|
let
|
|
# create the sender first, because it will modify networkParams
|
|
sender = TxSender.new(conf.networkParams, 30)
|
|
com = CommonRef.new(newCoreDbRef DefaultDbMemory,
|
|
nil, conf.networkId, conf.networkParams)
|
|
chain = ForkedChainRef.init(com)
|
|
|
|
TestEnv(
|
|
conf : conf,
|
|
com : com,
|
|
chain : chain,
|
|
xp : TxPoolRef.new(chain),
|
|
sender: sender
|
|
)
|
|
|
|
template checkAddTx(xp, tx, errorCode) =
|
|
let prevCount = xp.len
|
|
let rc = xp.addTx(tx)
|
|
check rc.isErr == true
|
|
if rc.isErr:
|
|
check rc.error == errorCode
|
|
check xp.len == prevCount
|
|
|
|
template checkAddTx(xp, tx) =
|
|
let expCount = xp.len + 1
|
|
let rc = xp.addTx(tx)
|
|
check rc.isOk == true
|
|
if rc.isErr:
|
|
debugEcho "ADD TX: ", rc.error
|
|
check xp.len == expCount
|
|
|
|
template checkAddTxSupersede(xp, tx) =
|
|
let prevCount = xp.len
|
|
let rc = xp.addTx(tx)
|
|
check rc.isOk == true
|
|
if rc.isErr:
|
|
debugEcho "ADD TX SUPERSEDE: ", rc.error
|
|
check xp.len == prevCount
|
|
|
|
template checkAssembleBlock(xp, expCount): auto =
|
|
xp.com.pos.timestamp = xp.com.pos.timestamp + 1
|
|
let rc = xp.assembleBlock()
|
|
check rc.isOk == true
|
|
if rc.isErr:
|
|
debugEcho "ASSEMBLE BLOCK: ", rc.error
|
|
if rc.isOk:
|
|
check rc.value.blk.transactions.len == expCount
|
|
rc.get
|
|
|
|
template checkImportBlock(xp: TxPoolRef, bundle: AssembledBlock) =
|
|
let rc = xp.chain.importBlock(bundle.blk)
|
|
check rc.isOk == true
|
|
if rc.isErr:
|
|
debugEcho "IMPORT BLOCK: ", rc.error
|
|
|
|
template checkImportBlock(xp: TxPoolRef, expCount: int, expRem: int) =
|
|
let bundle = checkAssembleBlock(xp, expCount)
|
|
checkImportBlock(xp, bundle)
|
|
xp.removeNewBlockTxs(bundle.blk)
|
|
check xp.len == expRem
|
|
|
|
template checkImportBlock(xp: TxPoolRef, expCount: int) =
|
|
let bundle = checkAssembleBlock(xp, expCount)
|
|
checkImportBlock(xp, bundle)
|
|
|
|
template checkImportBlock(xp: TxPoolRef, bundle: AssembledBlock, expRem: int) =
|
|
checkImportBlock(xp, bundle)
|
|
xp.removeNewBlockTxs(bundle.blk)
|
|
check xp.len == expRem
|
|
|
|
proc createPooledTransactionWithBlob(
|
|
mx: TxSender,
|
|
acc: TestAccount,
|
|
recipient: Address,
|
|
amount: UInt256,
|
|
nonce: AccountNonce
|
|
): PooledTransaction =
|
|
# Create the transaction
|
|
let
|
|
tc = BlobTx(
|
|
recipient: Opt.some(recipient),
|
|
gasLimit: 100000.GasInt,
|
|
gasTip: GasInt(10 ^ 9),
|
|
gasFee: GasInt(10 ^ 9),
|
|
blobGasFee: u256(1),
|
|
blobCount: 1,
|
|
blobID: 1,
|
|
)
|
|
|
|
mx.makeTx(tc, acc, nonce)
|
|
|
|
proc makeTx(
|
|
mx: TxSender,
|
|
acc: TestAccount,
|
|
recipient: Address,
|
|
amount: UInt256,
|
|
nonce: AccountNonce): PooledTransaction =
|
|
let
|
|
tc = BaseTx(
|
|
gasLimit: 75000,
|
|
recipient: Opt.some(recipient),
|
|
amount: amount,
|
|
)
|
|
mx.makeTx(tc, acc, nonce)
|
|
|
|
proc txPoolMain*() =
|
|
suite "TxPool test suite":
|
|
loadKzgTrustedSetup().expect("KZG trusted setup loaded")
|
|
let
|
|
env = initEnv(Cancun)
|
|
xp = env.xp
|
|
mx = env.sender
|
|
chain = env.chain
|
|
com = env.com
|
|
|
|
com.pos.prevRandao = prevRandao
|
|
com.pos.feeRecipient = feeRecipient
|
|
com.pos.timestamp = EthTime.now()
|
|
|
|
test "Bad blob tx":
|
|
let acc = mx.getAccount(7)
|
|
let tc = BlobTx(
|
|
txType: Opt.some(TxEip4844),
|
|
gasLimit: 75000,
|
|
recipient: Opt.some(acc.address),
|
|
blobID: 0.BlobID
|
|
)
|
|
var ptx = mx.makeTx(tc, 0)
|
|
var z = ptx.networkPayload.blobs[0]
|
|
z[0] = not z[0]
|
|
ptx.networkPayload.blobs[0] = z
|
|
xp.checkAddTx(ptx, txErrorInvalidBlob)
|
|
|
|
test "Bad chainId":
|
|
let acc = mx.getAccount(1)
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
var ptx = mx.makeTx(tc, 0)
|
|
let ccid = ptx.tx.chainId.uint64
|
|
let cid = Opt.some(ChainId(ccid.not))
|
|
ptx.tx = mx.customizeTransaction(acc, ptx.tx, CustomTx(chainId: cid))
|
|
xp.checkAddTx(ptx, txErrorChainIdMismatch)
|
|
|
|
test "Basic validation error, gas limit too low":
|
|
let tc = BaseTx(
|
|
gasLimit: 18000
|
|
)
|
|
let ptx = mx.makeTx(tc, 0)
|
|
xp.checkAddTx(ptx, txErrorBasicValidation)
|
|
|
|
test "Known tx":
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
let ptx = mx.makeNextTx(tc)
|
|
xp.checkAddTx(ptx)
|
|
xp.checkAddTx(ptx, txErrorAlreadyKnown)
|
|
xp.checkImportBlock(1, 0)
|
|
|
|
test "nonce too small":
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
let ptx = mx.makeNextTx(tc)
|
|
xp.checkAddTx(ptx)
|
|
xp.checkImportBlock(1, 0)
|
|
xp.checkAddTx(ptx, txErrorNonceTooSmall)
|
|
|
|
test "nonce gap after account nonce":
|
|
let acc = mx.getAccount(13)
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
let ptx1 = mx.makeTx(tc, acc, 1)
|
|
xp.checkAddTx(ptx1)
|
|
|
|
xp.checkImportBlock(0)
|
|
|
|
let ptx0 = mx.makeTx(tc, acc, 0)
|
|
xp.checkAddTx(ptx0)
|
|
|
|
xp.checkImportBlock(2, 0)
|
|
|
|
test "nonce gap in the middle of nonces":
|
|
let acc = mx.getAccount(14)
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
|
|
let ptx0 = mx.makeTx(tc, acc, 0)
|
|
xp.checkAddTx(ptx0)
|
|
|
|
let ptx2 = mx.makeTx(tc, acc, 2)
|
|
xp.checkAddTx(ptx2)
|
|
|
|
xp.checkImportBlock(1, 1)
|
|
|
|
let ptx1 = mx.makeTx(tc, acc, 1)
|
|
xp.checkAddTx(ptx1)
|
|
|
|
xp.checkImportBlock(2, 0)
|
|
|
|
test "supersede existing tx":
|
|
let acc = mx.getAccount(15)
|
|
let tc = BaseTx(
|
|
txType: Opt.some(TxLegacy),
|
|
gasLimit: 75000
|
|
)
|
|
|
|
var ptx = mx.makeTx(tc, acc, 0)
|
|
xp.checkAddTx(ptx)
|
|
|
|
let oldPrice = ptx.tx.gasPrice
|
|
ptx.tx = mx.customizeTransaction(acc, ptx.tx,
|
|
CustomTx(gasPriceOrGasFeeCap: Opt.some(oldPrice*2)))
|
|
xp.checkAddTxSupersede(ptx)
|
|
|
|
let bundle = xp.checkAssembleBlock(1)
|
|
check bundle.blk.transactions[0].gasPrice == oldPrice*2
|
|
xp.checkImportBlock(bundle, 0)
|
|
|
|
test "removeNewBlockTxs after two blocks":
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
|
|
var ptx = mx.makeNextTx(tc)
|
|
xp.checkAddTx(ptx)
|
|
|
|
xp.checkImportBlock(1)
|
|
|
|
ptx = mx.makeNextTx(tc)
|
|
xp.checkAddTx(ptx)
|
|
|
|
xp.checkImportBlock(1, 0)
|
|
|
|
test "max transactions per account":
|
|
let acc = mx.getAccount(16)
|
|
let tc = BaseTx(
|
|
txType: Opt.some(TxLegacy),
|
|
gasLimit: 75000
|
|
)
|
|
|
|
const MAX_TXS_GENERATED = 100
|
|
for i in 0..MAX_TXS_GENERATED-2:
|
|
let ptx = mx.makeTx(tc, acc, i.AccountNonce)
|
|
xp.checkAddTx(ptx)
|
|
|
|
var ptx = mx.makeTx(tc, acc, MAX_TXS_GENERATED-1)
|
|
xp.checkAddTx(ptx)
|
|
|
|
let ptxMax = mx.makeTx(tc, acc, MAX_TXS_GENERATED)
|
|
xp.checkAddTx(ptxMax, txErrorSenderMaxTxs)
|
|
|
|
# superseding not hit sender max txs
|
|
let oldPrice = ptx.tx.gasPrice
|
|
ptx.tx = mx.customizeTransaction(acc, ptx.tx,
|
|
CustomTx(gasPriceOrGasFeeCap: Opt.some(oldPrice*2)))
|
|
xp.checkAddTxSupersede(ptx)
|
|
|
|
var numTxsPacked = 0
|
|
while numTxsPacked < MAX_TXS_GENERATED:
|
|
com.pos.timestamp = com.pos.timestamp + 1
|
|
let bundle = xp.assembleBlock().valueOr:
|
|
debugEcho error
|
|
check false
|
|
return
|
|
|
|
numTxsPacked += bundle.blk.transactions.len
|
|
|
|
chain.importBlock(bundle.blk).isOkOr:
|
|
check false
|
|
debugEcho error
|
|
return
|
|
|
|
xp.removeNewBlockTxs(bundle.blk)
|
|
|
|
check xp.len == 0
|
|
|
|
test "auto remove lower nonces":
|
|
let xp2 = TxPoolRef.new(chain)
|
|
let acc = mx.getAccount(17)
|
|
let tc = BaseTx(
|
|
gasLimit: 75000
|
|
)
|
|
|
|
let ptx0 = mx.makeTx(tc, acc, 0)
|
|
xp.checkAddTx(ptx0)
|
|
xp2.checkAddTx(ptx0)
|
|
|
|
let ptx1 = mx.makeTx(tc, acc, 1)
|
|
xp.checkAddTx(ptx1)
|
|
xp2.checkAddTx(ptx1)
|
|
|
|
let ptx2 = mx.makeTx(tc, acc, 2)
|
|
xp.checkAddTx(ptx2)
|
|
|
|
xp2.checkImportBlock(2, 0)
|
|
xp.checkImportBlock(1, 0)
|
|
|
|
test "mixed type of transactions":
|
|
let acc = mx.getAccount(18)
|
|
let
|
|
tc1 = BaseTx(
|
|
txType: Opt.some(TxLegacy),
|
|
gasLimit: 75000
|
|
)
|
|
tc2 = BaseTx(
|
|
txType: Opt.some(TxEip1559),
|
|
gasLimit: 75000
|
|
)
|
|
tc3 = BaseTx(
|
|
txType: Opt.some(TxEip4844),
|
|
recipient: Opt.some(recipient),
|
|
gasLimit: 75000
|
|
)
|
|
|
|
let ptx0 = mx.makeTx(tc1, acc, 0)
|
|
let ptx1 = mx.makeTx(tc2, acc, 1)
|
|
let ptx2 = mx.makeTx(tc3, acc, 2)
|
|
|
|
xp.checkAddTx(ptx0)
|
|
xp.checkAddTx(ptx1)
|
|
xp.checkAddTx(ptx2)
|
|
xp.checkImportBlock(3, 0)
|
|
|
|
test "replacement gas too low":
|
|
let acc = mx.getAccount(19)
|
|
let
|
|
tc1 = BaseTx(
|
|
txType: Opt.some(TxLegacy),
|
|
gasLimit: 75000
|
|
)
|
|
tc2 = BaseTx(
|
|
txType: Opt.some(TxEip4844),
|
|
recipient: Opt.some(recipient),
|
|
gasLimit: 75000
|
|
)
|
|
|
|
var ptx0 = mx.makeTx(tc1, acc, 0)
|
|
var ptx1 = mx.makeTx(tc2, acc, 1)
|
|
|
|
xp.checkAddTx(ptx0)
|
|
xp.checkAddTx(ptx1)
|
|
|
|
var oldPrice = ptx0.tx.gasPrice
|
|
ptx0.tx = mx.customizeTransaction(acc, ptx0.tx,
|
|
CustomTx(gasPriceOrGasFeeCap: Opt.some(oldPrice+1)
|
|
))
|
|
xp.checkAddTx(ptx0, txErrorReplacementGasTooLow)
|
|
|
|
let oldGas = ptx1.tx.maxFeePerBlobGas
|
|
let oldTip = ptx1.tx.maxPriorityFeePerGas
|
|
oldPrice = ptx1.tx.maxFeePerGas
|
|
ptx1.tx = mx.customizeTransaction(acc, ptx1.tx,
|
|
CustomTx(blobGas: Opt.some(oldGas+1),
|
|
gasTipCap: Opt.some(oldTip*2),
|
|
gasPriceOrGasFeeCap: Opt.some(oldPrice*2)
|
|
))
|
|
xp.checkAddTx(ptx1, txErrorReplacementBlobGasTooLow)
|
|
xp.checkImportBlock(2, 0)
|
|
|
|
test "Test TxPool with PoS block":
|
|
let
|
|
acc = mx.getAccount(20)
|
|
tc = BaseTx(
|
|
txType: Opt.some(TxLegacy),
|
|
recipient: Opt.some(recipient),
|
|
gasLimit: 75000,
|
|
amount: amount,
|
|
)
|
|
ptx = mx.makeTx(tc, acc, 0)
|
|
|
|
xp.checkAddTx(ptx)
|
|
let bundle = xp.checkAssembleBlock(1)
|
|
xp.checkImportBlock(bundle, 0)
|
|
|
|
let
|
|
sdb = LedgerRef.init(com.db)
|
|
val = sdb.getStorage(recipient, slot)
|
|
randao = Bytes32(val.toBytesBE)
|
|
fee = sdb.getBalance(feeRecipient)
|
|
bal = sdb.getBalance(recipient)
|
|
|
|
check randao == prevRandao
|
|
check bundle.blk.header.coinbase == feeRecipient
|
|
check not fee.isZero
|
|
check bal >= 1000.u256
|
|
|
|
test "Test TxPool with blobhash block":
|
|
let
|
|
acc = mx.getAccount(21)
|
|
tx1 = mx.createPooledTransactionWithBlob(acc, recipient, amount, 0)
|
|
tx2 = mx.createPooledTransactionWithBlob(acc, recipient, amount, 1)
|
|
|
|
xp.checkAddTx(tx1)
|
|
xp.checkAddTx(tx2)
|
|
|
|
template header(): Header =
|
|
bundle.blk.header
|
|
|
|
let
|
|
bundle = xp.checkAssembleBlock(2)
|
|
gasUsed1 = xp.vmState.receipts[0].cumulativeGasUsed
|
|
gasUsed2 = xp.vmState.receipts[1].cumulativeGasUsed - gasUsed1
|
|
totalBlobGasUsed = tx1.tx.getTotalBlobGas + tx2.tx.getTotalBlobGas
|
|
blockValue =
|
|
gasUsed1.u256 * tx1.tx.effectiveGasTip(header.baseFeePerGas).u256 +
|
|
gasUsed2.u256 * tx2.tx.effectiveGasTip(header.baseFeePerGas).u256
|
|
|
|
check blockValue == bundle.blockValue
|
|
check totalBlobGasUsed == header.blobGasUsed.get()
|
|
|
|
xp.checkImportBlock(bundle, 0)
|
|
|
|
let
|
|
sdb = LedgerRef.init(com.db)
|
|
val = sdb.getStorage(recipient, slot)
|
|
randao = Bytes32(val.toBytesBE)
|
|
bal = sdb.getBalance(feeRecipient)
|
|
|
|
check randao == prevRandao
|
|
check header.coinbase == feeRecipient
|
|
check not bal.isZero
|
|
|
|
## see github.com/status-im/nimbus-eth1/issues/1031
|
|
test "TxPool: Synthesising blocks (covers issue #1031)":
|
|
const
|
|
txPerblock = 20
|
|
numBlocks = 10
|
|
|
|
let
|
|
lastNumber = chain.latestNumber
|
|
tc = BaseTx(
|
|
gasLimit: 75000,
|
|
recipient: Opt.some(recipient214),
|
|
amount: amount,
|
|
)
|
|
|
|
for n in 0 ..< numBlocks:
|
|
for tn in 0 ..< txPerblock:
|
|
let tx = mx.makeNextTx(tc)
|
|
xp.checkAddTx(tx)
|
|
|
|
xp.checkImportBlock(txPerblock, 0)
|
|
|
|
check com.syncCurrent == lastNumber + numBlocks
|
|
let
|
|
head = chain.headerByNumber(com.syncCurrent).expect("block header exists")
|
|
sdb = LedgerRef.init(com.db)
|
|
expected = u256(txPerblock * numBlocks) * amount
|
|
balance = sdb.getBalance(recipient214)
|
|
check balance == expected
|
|
discard head
|
|
|
|
test "Test get parent transactions after persistBlock":
|
|
let
|
|
acc = mx.getAccount(22)
|
|
tx1 = mx.makeTx(acc, recipient, 1.u256, 0)
|
|
tx2 = mx.makeTx(acc, recipient, 2.u256, 1)
|
|
|
|
xp.checkAddTx(tx1)
|
|
xp.checkAddTx(tx2)
|
|
|
|
xp.checkImportBlock(2, 0)
|
|
|
|
let
|
|
tx3 = mx.makeTx(acc, recipient, 3.u256, 2)
|
|
tx4 = mx.makeTx(acc, recipient, 4.u256, 3)
|
|
tx5 = mx.makeTx(acc, recipient, 5.u256, 4)
|
|
|
|
xp.checkAddTx(tx3)
|
|
xp.checkAddTx(tx4)
|
|
xp.checkAddTx(tx5)
|
|
|
|
xp.checkImportBlock(3, 0)
|
|
let latestHash = chain.latestHash
|
|
check env.chain.forkChoice(latestHash, latestHash).isOk
|
|
|
|
let hs = [
|
|
rlpHash(tx1),
|
|
rlpHash(tx2),
|
|
rlpHash(tx3),
|
|
rlpHash(tx4),
|
|
rlpHash(tx5),
|
|
]
|
|
|
|
let res = chain.blockByNumber(chain.latestHeader.number - 1)
|
|
if res.isErr:
|
|
debugEcho res.error
|
|
check false
|
|
|
|
let parent = res.get
|
|
var count = 0
|
|
for txh in chain.txHashInRange(latestHash, parent.header.parentHash):
|
|
check txh in hs
|
|
inc count
|
|
check count == hs.len
|
|
|
|
when isMainModule:
|
|
txPoolMain()
|