Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/testing/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ dependencies = [
"ckzg>=2.1.3,<3",
"tenacity>=9.0.0,<10",
"Jinja2>=3,<4",
"eth-account>=0.13.7",
"eth-utils>=5.3.1",
"eth-keys>=0.7.0",
]

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,18 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None:
Path("cli/pytest_commands/plugins/execute/execute_recover.py")
],
)

blob_sender = _create_execute_subcommand(
"blob-sender",
"pytest-execute-blob-sender.ini",
"Send blobs to a remote RPC endpoint.",
required_args=[
"--rpc-endpoint=http://localhost:8545",
"--rpc-seed-key=1",
"--chain-id=1",
"--fork=Cancun",
],
command_logic_test_paths=[
Path("cli/pytest_commands/plugins/execute/blob_sender/blob_sender.py")
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Execute module to send blobs."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
"""Blob sender plugin."""

import pytest
from eth_account import Account
from eth_keys import keys
from eth_utils import to_checksum_address

from execution_testing.base_types.base_types import Address, Bytes
from execution_testing.forks import ForkSetAdapter
from execution_testing.logging import (
get_logger,
)
from execution_testing.rpc import EthRPC
from execution_testing.test_types.blob_types import Blob

logger = get_logger(__name__)


def pytest_addoption(parser: pytest.Parser) -> None:
"""Add command-line options to pytest."""
blob_sender_group = parser.getgroup(
"blob_sender", "Arguments defining blob_sender behavior"
)
blob_sender_group.addoption(
"--blob-seed",
action="store",
dest="blob_seed",
required=False,
type=int,
default=1,
help=(
"Blob data is dynamically derived from this seed.\nNote: "
"This is the starting seed. If you send more than one blob, each "
"additional blob will have its seed increased by 1.\nMax value: 6"
),
)
blob_sender_group.addoption(
"--blob-amount",
action="store",
dest="blob_amount",
required=False,
type=int,
default=1,
help=("Amount of blobs to generate and send"),
)


# --------------------------- Helper functions --------------------------------


def hex_to_bytes(s: str) -> bytes:
"""Takes hex string and returns it as bytes."""
s = s[2:] if s[:2].lower() == "0x" else s
if len(s) % 2:
s = "0" + s
return bytes.fromhex(s)


def privkey_hex_to_addr(*, privkey_hex: str) -> str:
"""Takes private key hex string and returns derived checksum address."""
# convert hex to bytes
privkey_bytes = hex_to_bytes(privkey_hex)

# derive pubkey
pk = keys.PrivateKey(privkey_bytes)

# derive address
addr = pk.public_key.to_checksum_address()
return addr


def gwei_float_to_wei_int(gwei: float) -> int:
"""Convert gwei float to wei int."""
return int(gwei * (10**9))


# -----------------------------------------------------------------------------


@pytest.hookimpl(tryfirst=True)
def pytest_configure(config: pytest.Config) -> None:
"""
Set the provided command-line arguments.
"""
# skip validation if we're just showing help
if config.option.help:
return

blob_seed = config.getoption("blob_seed")

blob_amount = config.getoption("blob_amount")
assert blob_amount <= 6, (
"you may only send up to 6 blobs per tx, but you tried to "
f"send {blob_amount} blobs in one tx!"
)

fork_str = config.getoption("single_fork")
chain_id = config.getoption("chain_id")
rpc_endpoint = config.getoption("rpc_endpoint")

sender_privkey_hex = config.getoption("rpc_seed_key")
sender_address = privkey_hex_to_addr(privkey_hex=sender_privkey_hex)

if not fork_str:
pytest.exit(
"ERROR: --fork is required for blob-sender command.\n"
"Example Usage: uv run execute blob-sender -v -s --fork=Osaka --rpc-seed-key=0000000000000000000000000000000000000000000000000000000000000001 --rpc-endpoint=http://example.org --chain-id=11155111 --eest-log-level=INFO --blob-seed=5 --blob-amount=3", # noqa: E501
returncode=pytest.ExitCode.USAGE_ERROR,
)

# Convert fork string to Fork instance
fork_set = ForkSetAdapter.validate_python(fork_str)
fork = next(iter(fork_set))

# get sender nonce on target network
eth_rpc = EthRPC(rpc_endpoint)
nonce = eth_rpc.get_transaction_count(Address(sender_address))

logger.info(
"\nBlob Sender Plugin Configuration:"
f"\n\tFork: {fork}"
f"\n\tAmount of blobs to send: {blob_amount}"
f"\n\tStarting seed for blob generation: {blob_seed}"
f"\n\tSender Address: {sender_address}"
f"\n\tSender Nonce: {nonce}"
f"\n\tChain ID: {chain_id}"
)

versioned_hashes: list[Bytes] = []
blob_list: list[Bytes] = []
for current_seed in range(blob_seed, blob_seed + blob_amount):
print(f"Generating blob with seed {current_seed} for fork {fork}..")
b = Blob.from_fork(fork, seed=current_seed)
# blobs.append(b)
print("Successfully generated blob file:", b.name)

# extract relevant info from blob
data_hex = b.data.hex()
if data_hex.startswith("0x"):
data_hex = data_hex[2:]

assert len(data_hex) == 131072 * 2, (
f"Data should be 128KB, got {len(data_hex)} bytes"
)

blob_bytes = Bytes(data_hex)
blob_list.append(blob_bytes)

versioned_hashes.append(Bytes(b.versioned_hash))

# define type 3 tx
max_priority_fee_per_gas = 2 # gwei
max_fee_per_gas = 3.7 # gwei
max_fee_per_blob_gas = 6 # gwei

tx_dict = {
"type": 3, # EIP-4844 blob transaction
"chainId": chain_id,
"nonce": nonce,
"from": to_checksum_address(sender_address),
"to": to_checksum_address(
sender_address
), # just send it to yourself, on mainnet some L2's put "FF000....00<decimal-chainid>" as address # noqa: E501
"value": 0,
"gas": 21_000,
"maxPriorityFeePerGas": gwei_float_to_wei_int(
max_priority_fee_per_gas
),
"maxFeePerGas": gwei_float_to_wei_int(max_fee_per_gas),
"maxFeePerBlobGas": gwei_float_to_wei_int(max_fee_per_blob_gas),
"data": "0x",
"accessList": [],
"blobVersionedHashes": versioned_hashes,
}

signed_tx_obj = Account.sign_transaction(
tx_dict, sender_privkey_hex, blobs=blob_list
)
# signed_raw_tx_hex = "0x" + signed_tx_obj.raw_transaction.hex()
# logger.info(
# "done. you can send this now via "
# f"eth_sendRawTransaction: {signed_raw_tx_hex}"
# )

# send raw tx
raw_tx = Bytes(signed_tx_obj.raw_transaction)
tx_hash = eth_rpc.send_raw_transaction(raw_tx)
logger.info(f"\nSuccess!\nTx Hash: {tx_hash}")
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ def pytest_addoption(parser: pytest.Parser) -> None:
default=False,
help="Show help options specific to the execute's command eth_config and exit.",
)
help_group.addoption(
"--execute-blob-sender-help",
action="store_true",
dest="show_execute_blob_sender_help",
default=False,
help="Show help options for the execute blob-sender command and exit.",
)


@pytest.hookimpl(tryfirst=True)
Expand Down Expand Up @@ -113,6 +120,7 @@ def pytest_configure(config: pytest.Config) -> None:
"sender key fixtures",
"remote seed sender",
"chain configuration",
"blob sender",
],
)
elif config.getoption("show_execute_hive_help"):
Expand Down Expand Up @@ -148,6 +156,20 @@ def pytest_configure(config: pytest.Config) -> None:
"chain configuration",
],
)
elif config.getoption("show_execute_blob_sender_help"):
show_specific_help(
config,
"pytest-execute-blob-sender.ini",
[
"blob_sender",
"fork range",
"remote RPC configuration",
"pre-allocation behavior during test execution",
"sender key fixtures",
"remote seed sender",
"chain configuration",
],
)


def show_specific_help(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[pytest]
console_output_style = count
minversion = 7.0
python_files = test_*.py
testpaths = tests/
addopts =
-p execution_testing.cli.pytest_commands.plugins.execute.blob_sender.blob_sender
-p execution_testing.cli.pytest_commands.plugins.execute.execute_flags.execute_flags
-p execution_testing.cli.pytest_commands.plugins.execute.sender
-p execution_testing.cli.pytest_commands.plugins.execute.execute
-p execution_testing.cli.pytest_commands.plugins.shared.transaction_fixtures
-p execution_testing.cli.pytest_commands.plugins.execute.rpc.remote_seed_sender
-p execution_testing.cli.pytest_commands.plugins.execute.rpc.remote
-p execution_testing.cli.pytest_commands.plugins.forks.forks
-p execution_testing.cli.pytest_commands.plugins.help.help
-p execution_testing.cli.pytest_commands.plugins.custom_logging.plugin_logging
--tb short
--dist loadscope
--ignore tests/cancun/eip4844_blobs/point_evaluation_vectors/
--ignore tests/json_infra
--ignore tests/evm_tools
Loading
Loading