nimbus-eth1/tests/test_txpool.nim
andri lim 0ffc17d153
Merge test_txpool and test_txpool2 (#2983)
* Merge test_txpool and test_txpool2

* Fix copyright year
2025-01-05 08:05:29 +00:00

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()