ethereum.forks.shanghai.transactions

Transactions are atomic units of work created externally to Ethereum and submitted to be executed. If Ethereum is viewed as a state machine, transactions are the events that move between states.

LegacyTransaction

Atomic operation performed on the block chain. This represents the original transaction format used before EIP-1559, and EIP-2930.

32
@final
33
@slotted_freezable
34
@dataclass
class LegacyTransaction:

nonce

A scalar value equal to the number of transactions sent by the sender.

44
    nonce: U256

gas_price

The price of gas for this transaction, in wei.

49
    gas_price: Uint

gas

The maximum amount of gas that can be used by this transaction.

54
    gas: Uint

to

The address of the recipient. If empty, the transaction is a contract creation.

59
    to: Bytes0 | Address

value

The amount of ether (in wei) to send with this transaction.

65
    value: U256

data

The data payload of the transaction, which can be used to call functions on contracts or to create new contracts.

70
    data: Bytes

v

The recovery id of the signature.

76
    v: U256

r

The first part of the signature.

81
    r: U256

s

The second part of the signature.

86
    s: U256

Access

A mapping from account address to storage slots that are pre-warmed as part of a transaction.

92
@final
93
@slotted_freezable
94
@dataclass
class Access:

account

The address of the account that is accessed.

101
    account: Address

slots

A tuple of storage slots that are accessed in the account.

106
    slots: Tuple[Bytes32, ...]

AccessListTransaction

The transaction type added in EIP-2930 to support access lists.

This transaction type extends the legacy transaction with an access list and chain ID. The access list specifies which addresses and storage slots the transaction will access.

112
@final
113
@slotted_freezable
114
@dataclass
class AccessListTransaction:

chain_id

The ID of the chain on which this transaction is executed.

126
    chain_id: U64

nonce

A scalar value equal to the number of transactions sent by the sender.

131
    nonce: U256

gas_price

The price of gas for this transaction.

136
    gas_price: Uint

gas

The maximum amount of gas that can be used by this transaction.

141
    gas: Uint

to

The address of the recipient. If empty, the transaction is a contract creation.

146
    to: Bytes0 | Address

value

The amount of ether (in wei) to send with this transaction.

152
    value: U256

data

The data payload of the transaction, which can be used to call functions on contracts or to create new contracts.

157
    data: Bytes

access_list

A tuple of Access objects that specify which addresses and storage slots are accessed in the transaction.

163
    access_list: Tuple[Access, ...]

y_parity

The recovery id of the signature.

169
    y_parity: U256

r

The first part of the signature.

174
    r: U256

s

The second part of the signature.

179
    s: U256

FeeMarketTransaction

The transaction type added in EIP-1559.

This transaction type introduces a new fee market mechanism with two gas price parameters: max_priority_fee_per_gas and max_fee_per_gas.

185
@final
186
@slotted_freezable
187
@dataclass
class FeeMarketTransaction:

chain_id

The ID of the chain on which this transaction is executed.

198
    chain_id: U64

nonce

A scalar value equal to the number of transactions sent by the sender.

203
    nonce: U256

max_priority_fee_per_gas

The maximum priority fee per gas that the sender is willing to pay.

208
    max_priority_fee_per_gas: Uint

max_fee_per_gas

The maximum fee per gas that the sender is willing to pay, including the base fee and priority fee.

213
    max_fee_per_gas: Uint

gas

The maximum amount of gas that can be used by this transaction.

219
    gas: Uint

to

The address of the recipient. If empty, the transaction is a contract creation.

224
    to: Bytes0 | Address

value

The amount of ether (in wei) to send with this transaction.

230
    value: U256

data

The data payload of the transaction, which can be used to call functions on contracts or to create new contracts.

235
    data: Bytes

access_list

A tuple of Access objects that specify which addresses and storage slots are accessed in the transaction.

241
    access_list: Tuple[Access, ...]

y_parity

The recovery id of the signature.

247
    y_parity: U256

r

The first part of the signature.

252
    r: U256

s

The second part of the signature.

257
    s: U256

Transaction

Union type representing any valid transaction type.

263
Transaction = LegacyTransaction | AccessListTransaction | FeeMarketTransaction

encode_transaction

Encode a transaction into its RLP or typed transaction format. Needed because non-legacy transactions aren't RLP.

Legacy transactions are returned as-is, while other transaction types are prefixed with their type identifier and RLP encoded.

def encode_transaction(​tx: Transaction​) -> LegacyTransaction | Bytes:
270
    <snip>
277
    if isinstance(tx, LegacyTransaction):
278
        return tx
279
    elif isinstance(tx, AccessListTransaction):
280
        return b"\x01" + rlp.encode(tx)
281
    elif isinstance(tx, FeeMarketTransaction):
282
        return b"\x02" + rlp.encode(tx)
283
    else:
284
        raise Exception(f"Unable to encode transaction of type {type(tx)}")

decode_transaction

Decode a transaction from its RLP or typed transaction format. Needed because non-legacy transactions aren't RLP.

Legacy transactions are returned as-is, while other transaction types are decoded based on their type identifier prefix.

def decode_transaction(​tx: LegacyTransaction | Bytes​) -> Transaction:
288
    <snip>
295
    if isinstance(tx, Bytes):
296
        if len(tx) == 0:
297
            raise InvalidBlock("empty transaction")
298
        if tx[0] == 1:
299
            return rlp.decode_to(AccessListTransaction, tx[1:])
300
        elif tx[0] == 2:
301
            return rlp.decode_to(FeeMarketTransaction, tx[1:])
302
        else:
303
            raise TransactionTypeError(tx[0])
304
    else:
305
        return tx

validate_transaction

Verifies a transaction.

The gas in a transaction gets used to pay for the intrinsic cost of operations, therefore if there is insufficient gas then it would not be possible to execute a transaction and it will be declared invalid.

Additionally, the nonce of a transaction must not equal or exceed the limit defined in EIP-2681. In practice, defining the limit as 2**64-1 has no impact because sending 2**64-1 transactions is improbable. It's not strictly impossible though, 2**64-1 transactions is the entire capacity of the Ethereum blockchain at 2022 gas limits for a little over 22 years.

Also, the code size of a contract creation transaction must be within limits of the protocol.

This function takes a transaction as a parameter and returns the intrinsic gas cost of the transaction after validation. It throws an InsufficientTransactionGasError exception if the transaction does not provide enough gas to cover the intrinsic cost, and a NonceOverflowError exception if the nonce is greater than 2**64 - 2. It also raises an InitCodeTooLargeError if the code size of a contract creation transaction exceeds the maximum allowed size, and a PriorityFeeGreaterThanMaxFeeError if the maximum priority fee per gas of a fee market transaction exceeds its maximum fee per gas.

def validate_transaction(​tx: Transaction​) -> Uint:
309
    <snip>
338
    from .vm.interpreter import MAX_INIT_CODE_SIZE
339
340
    intrinsic_gas = calculate_intrinsic_cost(tx)
341
    if intrinsic_gas > tx.gas:
342
        raise InsufficientTransactionGasError("Insufficient gas")
343
    if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE:
344
        raise InitCodeTooLargeError("Code size too large")
345
    if U256(tx.nonce) >= U256(U64.MAX_VALUE):
346
        raise NonceOverflowError("Nonce too high")
347
    if isinstance(tx, FeeMarketTransaction):
348
        if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
349
            raise PriorityFeeGreaterThanMaxFeeError(
350
                "priority fee greater than max fee"
351
            )
352
353
    return intrinsic_gas

calculate_intrinsic_cost

Calculates the gas that is charged before execution is started.

The intrinsic cost of the transaction is charged before execution has begun. Functions/operations in the EVM cost money to execute so this intrinsic cost is for the operations that need to be paid for as part of the transaction. Data transfer, for example, is part of this intrinsic cost. It costs ether to send data over the wire and that ether is accounted for in the intrinsic cost calculated in this function. This intrinsic cost must be calculated and paid for before execution in order for all operations to be implemented.

The intrinsic cost includes:

  1. Base cost (TX_BASE)

  2. Cost for data (zero and non-zero bytes)

  3. Cost for contract creation (if applicable)

  4. Cost for access list entries (if applicable)

This function takes a transaction as a parameter and returns the intrinsic gas cost of the transaction.

def calculate_intrinsic_cost(​tx: Transaction​) -> Uint:
357
    <snip>
378
    from .vm.gas import GasCosts, init_code_cost
379
380
    num_zeros = Uint(tx.data.count(0))
381
    num_non_zeros = ulen(tx.data) - num_zeros
382
    data_cost = (
383
        num_zeros * GasCosts.TX_DATA_PER_ZERO
384
        + num_non_zeros * GasCosts.TX_DATA_PER_NON_ZERO
385
    )
386
387
    if tx.to == Bytes0(b""):
388
        create_cost = GasCosts.TX_CREATE + init_code_cost(ulen(tx.data))
389
    else:
390
        create_cost = Uint(0)
391
392
    access_list_cost = Uint(0)
393
    if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)):
394
        for access in tx.access_list:
395
            access_list_cost += GasCosts.TX_ACCESS_LIST_ADDRESS
396
            access_list_cost += (
397
                ulen(access.slots) * GasCosts.TX_ACCESS_LIST_STORAGE_KEY
398
            )
399
400
    return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost

chain_id

Extract the chain identifier from a transaction. See EIP-155.

def chain_id(​tx: Transaction​) -> None | U64:
404
    <snip>
409
    if isinstance(tx, LegacyTransaction):
410
        if tx.v == 27 or tx.v == 28:
411
            return None
412
413
        if tx.v < U256(35):
414
            raise InvalidSignatureError("bad v")
415
416
        tx_chain_id = (tx.v - U256(35)) >> U256(1)
417
        if tx_chain_id > U256(U64.MAX_VALUE):
418
            raise InvalidSignatureError("bad v")
419
        return U64(tx_chain_id)
420
    else:
421
        return tx.chain_id

recover_sender

Extracts the sender address from a transaction.

The v, r, and s values are the three parts that make up the signature of a transaction. In order to recover the sender of a transaction the two components needed are the signature (v, r, and s) and the signing hash of the transaction. The sender's public key can be obtained with these two values and therefore the sender address can be retrieved.

This function takes chain_id and a transaction as parameters and returns the address of the sender of the transaction. It raises an InvalidSignatureError if the signature values (r, s, v) are invalid.

def recover_sender(​tx: Transaction​) -> Address:
425
    <snip>
438
    r, s = tx.r, tx.s
439
    if U256(0) >= r or r >= SECP256K1N:
440
        raise InvalidSignatureError("bad r")
441
    if U256(0) >= s or s > SECP256K1N // U256(2):
442
        raise InvalidSignatureError("bad s")
443
444
    if isinstance(tx, LegacyTransaction):
445
        v = tx.v
446
        if v == 27 or v == 28:
447
            public_key = secp256k1_recover(
448
                r, s, v - U256(27), signing_hash_pre155(tx)
449
            )
450
        else:
451
            assert v >= U256(35), "call chain_id before recover_sender"
452
            tx_chain_id = U64((v - U256(35)) >> U256(1))
453
            v = (v - U256(35)) & U256(1)
454
            public_key = secp256k1_recover(
455
                r,
456
                s,
457
                v,
458
                signing_hash_155(tx, tx_chain_id),
459
            )
460
    elif isinstance(tx, AccessListTransaction):
461
        if tx.y_parity not in (U256(0), U256(1)):
462
            raise InvalidSignatureError("bad y_parity")
463
        public_key = secp256k1_recover(
464
            r, s, tx.y_parity, signing_hash_2930(tx)
465
        )
466
    elif isinstance(tx, FeeMarketTransaction):
467
        if tx.y_parity not in (U256(0), U256(1)):
468
            raise InvalidSignatureError("bad y_parity")
469
        public_key = secp256k1_recover(
470
            r, s, tx.y_parity, signing_hash_1559(tx)
471
        )
472
473
    return Address(keccak256(public_key)[12:32])

signing_hash_pre155

Compute the hash of a transaction used in a legacy (pre EIP-155) signature.

This function takes a legacy transaction as a parameter and returns the signing hash of the transaction.

def signing_hash_pre155(​tx: LegacyTransaction​) -> Hash32:
477
    <snip>
486
    return keccak256(
487
        rlp.encode(
488
            (
489
                tx.nonce,
490
                tx.gas_price,
491
                tx.gas,
492
                tx.to,
493
                tx.value,
494
                tx.data,
495
            )
496
        )
497
    )

signing_hash_155

Compute the hash of a transaction used in a EIP-155 signature.

This function takes a legacy transaction and a chain ID as parameters and returns the hash of the transaction used in an EIP-155 signature.

def signing_hash_155(​tx: LegacyTransaction, ​​chain_id: U64​) -> Hash32:
501
    <snip>
509
    return keccak256(
510
        rlp.encode(
511
            (
512
                tx.nonce,
513
                tx.gas_price,
514
                tx.gas,
515
                tx.to,
516
                tx.value,
517
                tx.data,
518
                chain_id,
519
                Uint(0),
520
                Uint(0),
521
            )
522
        )
523
    )

signing_hash_2930

Compute the hash of a transaction used in a EIP-2930 signature.

This function takes an access list transaction as a parameter and returns the hash of the transaction used in an EIP-2930 signature.

def signing_hash_2930(​tx: AccessListTransaction​) -> Hash32:
527
    <snip>
535
    return keccak256(
536
        b"\x01"
537
        + rlp.encode(
538
            (
539
                tx.chain_id,
540
                tx.nonce,
541
                tx.gas_price,
542
                tx.gas,
543
                tx.to,
544
                tx.value,
545
                tx.data,
546
                tx.access_list,
547
            )
548
        )
549
    )

signing_hash_1559

Compute the hash of a transaction used in an EIP-1559 signature.

This function takes a fee market transaction as a parameter and returns the hash of the transaction used in an EIP-1559 signature.

def signing_hash_1559(​tx: FeeMarketTransaction​) -> Hash32:
553
    <snip>
561
    return keccak256(
562
        b"\x02"
563
        + rlp.encode(
564
            (
565
                tx.chain_id,
566
                tx.nonce,
567
                tx.max_priority_fee_per_gas,
568
                tx.max_fee_per_gas,
569
                tx.gas,
570
                tx.to,
571
                tx.value,
572
                tx.data,
573
                tx.access_list,
574
            )
575
        )
576
    )

get_transaction_hash

Compute the hash of a transaction.

This function takes a transaction as a parameter and returns the keccak256 hash of the transaction. It can handle both legacy transactions and typed transactions (AccessListTransaction, FeeMarketTransaction, etc.).

def get_transaction_hash(​tx: Bytes | LegacyTransaction​) -> Hash32:
580
    <snip>
588
    assert isinstance(tx, (LegacyTransaction, Bytes))
589
    if isinstance(tx, LegacyTransaction):
590
        return keccak256(rlp.encode(tx))
591
    else:
592
        return keccak256(tx)