205 lines
6.9 KiB
Python
205 lines
6.9 KiB
Python
import sys
|
|
|
|
import pytest
|
|
|
|
from .network import setup_ethermint
|
|
from .utils import ADDRS, KEYS, eth_to_bech32, sign_transaction, wait_for_new_blocks
|
|
|
|
PRIORITY_REDUCTION = 1000000
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def custom_ethermint(tmp_path_factory):
|
|
path = tmp_path_factory.mktemp("priority")
|
|
yield from setup_ethermint(path, 26800, long_timeout_commit=True)
|
|
|
|
|
|
def effective_gas_price(tx, base_fee):
|
|
if "maxFeePerGas" in tx:
|
|
# dynamic fee tx
|
|
return min(base_fee + tx["maxPriorityFeePerGas"], tx["maxFeePerGas"])
|
|
else:
|
|
# legacy tx
|
|
return tx["gasPrice"]
|
|
|
|
|
|
def tx_priority(tx, base_fee):
|
|
if "maxFeePerGas" in tx:
|
|
# dynamic fee tx
|
|
return (
|
|
min(tx["maxPriorityFeePerGas"], tx["maxFeePerGas"] - base_fee)
|
|
// PRIORITY_REDUCTION
|
|
)
|
|
else:
|
|
# legacy tx
|
|
return (tx["gasPrice"] - base_fee) // PRIORITY_REDUCTION
|
|
|
|
|
|
def test_priority(ethermint):
|
|
"""
|
|
test priorities of different tx types
|
|
|
|
use a relatively large priority number to counter
|
|
the effect of base fee change during the testing.
|
|
"""
|
|
w3 = ethermint.w3
|
|
amount = 10000
|
|
base_fee = w3.eth.get_block("latest").baseFeePerGas
|
|
|
|
# [ ( sender, tx ), ... ]
|
|
# use different senders to avoid nonce conflicts
|
|
test_cases = [
|
|
(
|
|
"validator",
|
|
{
|
|
"to": "0x0000000000000000000000000000000000000000",
|
|
"value": amount,
|
|
"gas": 21000,
|
|
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
|
|
"maxPriorityFeePerGas": 0,
|
|
},
|
|
),
|
|
(
|
|
"community",
|
|
{
|
|
"to": "0x0000000000000000000000000000000000000000",
|
|
"value": amount,
|
|
"gas": 21000,
|
|
"gasPrice": base_fee + PRIORITY_REDUCTION * 200000,
|
|
},
|
|
),
|
|
(
|
|
"signer2",
|
|
{
|
|
"to": "0x0000000000000000000000000000000000000000",
|
|
"value": amount,
|
|
"gasPrice": base_fee + PRIORITY_REDUCTION * 400000,
|
|
"accessList": [
|
|
{
|
|
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
|
"storageKeys": (
|
|
"0x00000000000000000000000000000000000000000000000000000000"
|
|
"00000003",
|
|
"0x00000000000000000000000000000000000000000000000000000000"
|
|
"00000007",
|
|
),
|
|
}
|
|
],
|
|
},
|
|
),
|
|
(
|
|
"signer1",
|
|
{
|
|
"to": "0x0000000000000000000000000000000000000000",
|
|
"value": amount,
|
|
"gas": 21000,
|
|
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
|
|
"maxPriorityFeePerGas": PRIORITY_REDUCTION * 600000,
|
|
},
|
|
),
|
|
]
|
|
|
|
# test cases are ordered by priority
|
|
expect_priorities = [tx_priority(tx, base_fee) for _, tx in test_cases]
|
|
assert expect_priorities == [0, 200000, 400000, 600000]
|
|
|
|
signed = [sign_transaction(w3, tx, key=KEYS[sender]) for sender, tx in test_cases]
|
|
# send the txs from low priority to high,
|
|
# but the later sent txs should be included earlier.
|
|
txhashes = [w3.eth.send_raw_transaction(tx.rawTransaction) for tx in signed]
|
|
|
|
receipts = [w3.eth.wait_for_transaction_receipt(txhash) for txhash in txhashes]
|
|
print(receipts)
|
|
assert all(receipt.status == 1 for receipt in receipts), "expect all txs success"
|
|
|
|
# the later txs should be included earlier because of higher priority
|
|
# FIXME there's some non-deterministics due to mempool logic
|
|
tx_indexes = [(r.blockNumber, r.transactionIndex) for r in receipts]
|
|
print(tx_indexes)
|
|
# the first sent tx are included later, because of lower priority
|
|
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
|
|
|
|
|
|
def test_native_tx_priority(ethermint):
|
|
cli = ethermint.cosmos_cli()
|
|
base_fee = cli.query_base_fee()
|
|
print("base_fee", base_fee)
|
|
test_cases = [
|
|
{
|
|
"from": eth_to_bech32(ADDRS["community"]),
|
|
"to": eth_to_bech32(ADDRS["validator"]),
|
|
"amount": "1000aphoton",
|
|
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
|
"max_priority_price": 0,
|
|
},
|
|
{
|
|
"from": eth_to_bech32(ADDRS["signer1"]),
|
|
"to": eth_to_bech32(ADDRS["signer2"]),
|
|
"amount": "1000aphoton",
|
|
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
|
"max_priority_price": PRIORITY_REDUCTION * 200000,
|
|
},
|
|
{
|
|
"from": eth_to_bech32(ADDRS["signer2"]),
|
|
"to": eth_to_bech32(ADDRS["signer1"]),
|
|
"amount": "1000aphoton",
|
|
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 400000}aphoton",
|
|
"max_priority_price": PRIORITY_REDUCTION * 400000,
|
|
},
|
|
{
|
|
"from": eth_to_bech32(ADDRS["validator"]),
|
|
"to": eth_to_bech32(ADDRS["community"]),
|
|
"amount": "1000aphoton",
|
|
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
|
"max_priority_price": None, # no extension, maximum tipFeeCap
|
|
},
|
|
]
|
|
txs = []
|
|
expect_priorities = []
|
|
for tc in test_cases:
|
|
tx = cli.transfer(
|
|
tc["from"],
|
|
tc["to"],
|
|
tc["amount"],
|
|
gas_prices=tc["gas_prices"],
|
|
generate_only=True,
|
|
)
|
|
txs.append(
|
|
cli.sign_tx_json(
|
|
tx, tc["from"], max_priority_price=tc.get("max_priority_price")
|
|
)
|
|
)
|
|
gas_price = int(tc["gas_prices"].removesuffix("aphoton"))
|
|
expect_priorities.append(
|
|
min(
|
|
get_max_priority_price(tc.get("max_priority_price")),
|
|
gas_price - base_fee,
|
|
)
|
|
// PRIORITY_REDUCTION
|
|
)
|
|
assert expect_priorities == [0, 200000, 400000, 600000]
|
|
|
|
txhashes = []
|
|
for tx in txs:
|
|
rsp = cli.broadcast_tx_json(tx, broadcast_mode="sync")
|
|
assert rsp["code"] == 0, rsp["raw_log"]
|
|
txhashes.append(rsp["txhash"])
|
|
|
|
print("wait for two new blocks, so the sent txs are all included")
|
|
wait_for_new_blocks(cli, 2)
|
|
|
|
tx_results = [cli.tx_search_rpc(f"tx.hash='{txhash}'")[0] for txhash in txhashes]
|
|
tx_indexes = [(int(r["height"]), r["index"]) for r in tx_results]
|
|
print(tx_indexes)
|
|
# the first sent tx are included later, because of lower priority
|
|
# ensure desc within continuous block
|
|
assert all(
|
|
(b1 < b2 or (b1 == b2 and i1 > i2))
|
|
for (b1, i1), (b2, i2) in zip(tx_indexes, tx_indexes[1:])
|
|
)
|
|
|
|
|
|
def get_max_priority_price(max_priority_price):
|
|
"default to max int64 if None"
|
|
return max_priority_price if max_priority_price is not None else sys.maxsize
|