Skip to content

Commit

Permalink
Implement Blob transactions (type-3) and BLOBHASH opcode (#50)
Browse files Browse the repository at this point in the history
* Implement Blob transactions

* Add new header fields
blob_gas_used and excess_blob_gas

* Add missing 'BlockBlobBaseFee' in interpreter initialization

* Implement BLOBHASH opcode

* Add BlobTransactionRlp for the parsing phase of the test runner

* Apply review

* Reduce BytePacking overhead for txn routing

* Update BlobTransactionRlp as per EIP specification

* Add tests
  • Loading branch information
Nashtare authored Feb 21, 2024
1 parent d43c8e2 commit 8926efd
Show file tree
Hide file tree
Showing 27 changed files with 548 additions and 42 deletions.
1 change: 1 addition & 0 deletions evm_arithmetization/src/cpu/kernel/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/transactions/type_0.asm"),
include_str!("asm/transactions/type_1.asm"),
include_str!("asm/transactions/type_2.asm"),
include_str!("asm/transactions/type_3.asm"),
include_str!("asm/util/assertions.asm"),
include_str!("asm/util/basic_macros.asm"),
include_str!("asm/util/keccak.asm"),
Expand Down
6 changes: 3 additions & 3 deletions evm_arithmetization/src/cpu/kernel/asm/core/exception.asm
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ min_stack_len_for_opcode:
BYTES 0 // 0x46, CHAINID
BYTES 0 // 0x47, SELFBALANCE
BYTES 0 // 0x48, BASEFEE
BYTES 0 // 0x49, invalid
BYTES 1 // 0x49, BLOBHASH
BYTES 0 // 0x4a, BLOBBASEFEE
%rep 5 // 0x4b-0x4f, invalid
BYTES 0
Expand Down Expand Up @@ -371,11 +371,11 @@ gas_cost_for_opcode:
BYTES 0
%endrep

%rep 25 //0x30-0x48, only syscalls
%rep 26 //0x30-0x49, only syscalls
BYTES 0
%endrep

%rep 7 // 0x49-0x4f, invalid
%rep 6 // 0x4a-0x4f, invalid
BYTES 0
%endrep

Expand Down
2 changes: 1 addition & 1 deletion evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ global syscall_jumptable:
JUMPTABLE sys_chainid
JUMPTABLE sys_selfbalance
JUMPTABLE sys_basefee
JUMPTABLE panic // 0x49 is invalid
JUMPTABLE sys_blobhash
JUMPTABLE sys_blobbasefee
%rep 5
JUMPTABLE panic // 0x4b-0x4f are invalid opcodes
Expand Down
33 changes: 33 additions & 0 deletions evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,39 @@ global sys_basefee:
SWAP1
EXIT_KERNEL

global sys_blobhash:
// stack: kexit_info, index
%charge_gas_const(@GAS_HASH_OPCODE)
// stack: kexit_info, index
%blobhash
// stack: blobhash, kexit_info
SWAP1
EXIT_KERNEL

%macro blobhash
// stack: kexit_info, index
SWAP1
// stack: index, kexit_info
%mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN)
DUP2
LT ISZERO // == GE
// stack: index >= len, index, kexit_info
%jumpi(%%index_too_big)
PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES
%build_kernel_address
// stack: read_addr, kexit_info
MLOAD_GENERAL
%jump(%%end)
%%index_too_big:
// The index is larger than the list, just push 0.
// stack: index, kexit_info
POP
PUSH 0
// stack: 0, kexit_info
%%end:
// stack: blobhash, kexit_info
%endmacro

global sys_blobbasefee:
// stack: kexit_info
%charge_gas_const(@GAS_BASE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,58 @@
%%after:
%endmacro

%macro decode_and_store_max_fee_per_blob_gas
// stack: rlp_addr
%decode_rlp_scalar
%stack (rlp_addr, max_fee_per_blob_gas) -> (max_fee_per_blob_gas, rlp_addr)
%mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS)
// stack: rlp_addr
%endmacro

%macro decode_and_store_blob_versioned_hashes
// stack: rlp_addr
DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START)
%decode_rlp_list_len
%stack (rlp_addr, len) -> (len, len, rlp_addr, %%after)
%jumpi(decode_and_store_blob_versioned_hashes)
// stack: len, rlp_addr, %%after
POP SWAP1 POP
// stack: rlp_addr
%mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN)
%%after:
%endmacro

// The blob versioned hashes are just a list of hashes.
global decode_and_store_blob_versioned_hashes:
// stack: len, rlp_addr
// Store the list length
DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN)

// stack: len, rlp_addr
DUP2 ADD
// stack: end_rlp_addr, rlp_addr
// Store the RLP length.
%mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN)
// stack: end_rlp_addr, rlp_addr
PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES // initial address to write to
SWAP2
decode_and_store_blob_versioned_hashes_loop:
// stack: rlp_addr, end_rlp_addr, store_addr
DUP2 DUP2 EQ %jumpi(decode_and_store_blob_versioned_hashes_finish)
// stack: rlp_addr, end_rlp_addr, store_addr
%decode_rlp_scalar // blob_versioned_hashes[i]
// stack: rlp_addr, hash, end_rlp_addr, store_addr
SWAP3 DUP1 SWAP2
// stack: hash, store_addr, store_addr, end_rlp_addr, rlp_addr
MSTORE_GENERAL
// stack: store_addr, end_rlp_addr, rlp_addr
%increment SWAP2
// stack: rlp_addr, end_rlp_addr, store_addr'
%jump(decode_and_store_blob_versioned_hashes_loop)
decode_and_store_blob_versioned_hashes_finish:
%stack (rlp_addr, end_rlp_addr, store_addr, retdest) -> (retdest, rlp_addr)
JUMP

%macro decode_and_store_y_parity
// stack: rlp_addr
%decode_rlp_scalar
Expand Down
22 changes: 15 additions & 7 deletions evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,33 @@ read_txn_from_memory:
// stack: retdest

// We will peak at the first byte to determine what type of transaction this is.
// Note that type 1 and 2 transactions have a first byte of 1 and 2, respectively.
// Note that type 1, 2 and 3 transactions have a first byte of 1, 2 and 3, respectively.
// Type 0 (legacy) transactions have no such prefix, but their RLP will have a
// first byte >= 0xc0, so there is no overlap.

PUSH @SEGMENT_RLP_RAW // ctx == virt == 0
DUP1
MLOAD_GENERAL
%eq_const(1)
// stack: first_byte == 1, retdest
// stack: first_byte == 1, rlp_segment, retdest
%jumpi(process_type_1_txn)
// stack: retdest
// stack: rlp_segment, retdest

PUSH @SEGMENT_RLP_RAW // ctx == virt == 0
DUP1
MLOAD_GENERAL
%eq_const(2)
// stack: first_byte == 2, retdest
// stack: first_byte == 2, rlp_segment, retdest
%jumpi(process_type_2_txn)
// stack: retdest
// stack: rlp_segment, retdest

DUP1
MLOAD_GENERAL
%eq_const(3)
// stack: first_byte == 3, rlp_segment, retdest
%jumpi(process_type_3_txn)
// stack: rlp_segment, retdest

// At this point, since it's not a type 1 or 2 transaction,
// At this point, since it's not a type 1, 2 or 3 transaction,
// it must be a legacy (aka type 0) transaction.
%jump(process_type_0_txn)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// keccak256(rlp([nonce, gas_price, gas_limit, to, value, data]))

global process_type_0_txn:
// stack: retdest
PUSH @SEGMENT_RLP_RAW // ctx == virt == 0
// stack: rlp_addr, retdest
%decode_rlp_list_len
// We don't actually need the length.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
// data, access_list]))

global process_type_1_txn:
// stack: retdest
// stack: rlp_addr, retdest
// Initial rlp address offset of 1 (skipping over the 0x01 byte)
PUSH 1
PUSH @SEGMENT_RLP_RAW
%build_kernel_address
%add_const(1)
// stack: rlp_addr, retdest
%decode_rlp_list_len
// We don't actually need the length.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
// access_list]))

global process_type_2_txn:
// stack: retdest
// stack: rlp_addr, retdest
// Initial rlp address offset of 1 (skipping over the 0x02 byte)
PUSH 1
PUSH @SEGMENT_RLP_RAW
%build_kernel_address
%add_const(1)
// stack: rlp_addr, retdest
%decode_rlp_list_len
// We don't actually need the length.
Expand Down
157 changes: 157 additions & 0 deletions evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Type 3 transactions, introduced by EIP 4844, have the format
// 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
// data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s])
//
// The signed data is
// keccak256(0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit,
// to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))

global process_type_3_txn:
// stack: rlp_addr, retdest
// Initial rlp address offset of 1 (skipping over the 0x03 byte)
%add_const(1)
// stack: rlp_addr, retdest
%decode_rlp_list_len
// We don't actually need the length.
%stack (rlp_addr, len) -> (rlp_addr)

// stack: rlp_addr, retdest
%store_chain_id_present_true
%decode_and_store_chain_id
%decode_and_store_nonce
%decode_and_store_max_priority_fee
%decode_and_store_max_fee
%decode_and_store_gas_limit
%decode_and_store_to
%decode_and_store_value
%decode_and_store_data
%decode_and_store_access_list
%decode_and_store_max_fee_per_blob_gas
%decode_and_store_blob_versioned_hashes
%decode_and_store_y_parity
%decode_and_store_r
%decode_and_store_s

// stack: rlp_addr, retdest
POP
// stack: retdest

// From EIP-4844:
// The signature_y_parity, signature_r, signature_s elements of this transaction represent a secp256k1 signature over
// keccak256(0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))
type_3_compute_signed_data:
%alloc_rlp_block
// stack: rlp_addr_start, retdest
%mload_txn_field(@TXN_FIELD_CHAIN_ID)
// stack: chain_id, rlp_start, retdest
DUP2
// stack: rlp_addr, chain_id, rlp_start, retdest
%encode_rlp_scalar
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_NONCE)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_GAS_LIMIT)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

// As per EIP-4844, blob transactions cannot have the form of a create transaction.
%mload_txn_field(@TXN_FIELD_TO)
%mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %jumpi(panic)
// stack: to, rlp_addr, rlp_start, retdest
SWAP1 %encode_rlp_160
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_VALUE)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

// Encode txn data.
%mload_txn_field(@TXN_FIELD_DATA_LEN)
PUSH @SEGMENT_TXN_DATA // ctx == virt == 0
// stack: ADDR, len, rlp_addr, rlp_start, retdest
PUSH after_serializing_txn_data
// stack: after_serializing_txn_data, ADDR, len, rlp_addr, rlp_start, retdest
SWAP3
// stack: rlp_addr, ADDR, len, after_serializing_txn_data, rlp_start, retdest
%jump(encode_rlp_string)

after_serializing_txn_data:
// Instead of manually encoding the access list, we just copy the raw RLP from the transaction.
%mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START)
%mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN)
%stack (al_len, al_start, rlp_addr, rlp_start, retdest) ->
(
rlp_addr,
al_start,
al_len,
after_serializing_access_list,
rlp_addr, rlp_start, retdest)
%jump(memcpy_bytes)
after_serializing_access_list:
// stack: rlp_addr, rlp_start, retdest
%mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) ADD
// stack: rlp_addr, rlp_start, retdest

%mload_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS)
%encode_rlp_scalar_swapped_inputs
// stack: rlp_addr, rlp_start, retdest

// Instead of manually encoding the blob versioned hashes, we just copy the raw RLP from the transaction.
%mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START)
%mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN)
%stack (bvh_len, bvh_start, rlp_addr, rlp_start, retdest) ->
(
rlp_addr,
bvh_start,
bvh_len,
after_serializing_blob_versioned_hashes,
rlp_addr, rlp_start, retdest)
%jump(memcpy_bytes)
after_serializing_blob_versioned_hashes:
%prepend_rlp_list_prefix
// stack: prefix_start_pos, rlp_len, retdest

// Store a `3` in front of the RLP
%decrement
%stack (rlp_addr) -> (3, rlp_addr, rlp_addr)
MSTORE_GENERAL
// stack: rlp_addr, rlp_len, retdest

// Hash the RLP + the leading `3`
SWAP1 %increment SWAP1
// stack: ADDR, len, retdest
KECCAK_GENERAL
// stack: hash, retdest

%mload_txn_field(@TXN_FIELD_S)
%mload_txn_field(@TXN_FIELD_R)
%mload_txn_field(@TXN_FIELD_Y_PARITY) %add_const(27) // ecrecover interprets v as y_parity + 27

PUSH store_origin
// stack: store_origin, v, r, s, hash, retdest
SWAP4
// stack: hash, v, r, s, store_origin, retdest
%jump(ecrecover)

store_origin:
// stack: address, retdest
// If ecrecover returned u256::MAX, that indicates failure.
DUP1
%eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
%jumpi(panic)

// stack: address, retdest
%mstore_txn_field(@TXN_FIELD_ORIGIN)
// stack: retdest
%jump(process_normalized_txn)
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub(crate) const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges(&[
0x0c..=0x0f,
0x1e..=0x1f,
0x21..=0x2f,
0x49..=0x4f,
0x4a..=0x4f,
0x5c..=0x5e,
0xa5..=0xef,
0xf6..=0xf9,
Expand Down
Loading

0 comments on commit 8926efd

Please sign in to comment.