Ethereum Pectra 업그레이드

한나리·2025년 8월 27일

blockchain

목록 보기
7/7
post-thumbnail

개요

2025년 5월 7일에 이루어진 이더리움 펙트라 업그레이드는, 이더리움의 execution layer와 consensus layer의 주요 업그레이드이다.

이번 업그레이드는 그동안에 업그레이드에 비해서 가장 많은 EIP를 포함한다.

관련 업그레이드
2022 더 머지(The Merge) 업그레이드 (지분증명 전환)
2024 덴쿤(Dencun) 업그레이드 (Blob 도입)

배경 및 업그레이드 필요성

더 머지를 통해 지분 증명 방식으로 전환했고 덴쿤 업그레이드로 확정성을 개선했다. 하지만 여전히 몇가지 문제가 존재했다.

  • 높은 거래 수수료
  • 확장성 부족
  • 어려운 사용 방식

따라서 이번 업그레이드는 이러한 문제에 대한 개선을 목표로 삼고 있다.

  • 네트워크 효율성 증대
  • 스마트 컨트랙트 개선
  • 검증인 시스템 최적화
  • 레이어2 확장성 향상

특히 스토리지 효율성을 높이고 검증인 참여를 원활하게 만드는 변화가 포함되어 있다.

주요 개선 사항

항목설명
EIP-7702EOA의 스마트 계약화로 계정 추상화 지원
EIP-7251밸리데이터 스테이킹 한도 2,048 ETH로 증가
EIP-6110밸리데이터 입금 처리 시간 13분으로 단축
EIP-7002실행 계층에서 출금 트리거 가능
EIP-7691블롭 처리량 증가로 레이어 2 확장성 향상
EIP-2537BLS 연산 최적화로 성능 및 보안 강화
EIP-2935과거 블록 해시 저장으로 스마트 계약 기능 향상
EIP-7840동적 블롭 스케일링
EIP-7623콜데이터 비용 관리
EIP-7549최적화된 증명
EIP-7685합의 계층 최적화 / 블록 검증 효율 및 네트워크 최적화

주요 개선 사항 11가지를 특징에에 따라 카테고리화 하여 설명한다.

  • Proof of Stake (PoS) / Consensus
EIP제목설명
EIP-7251스테이킹 상한 상향 (2048ETH)단일 밸리데이터가 2048 ETH까지 스테이킹 가능. 대규모 밸리데이터 운영 간소화 및 네트워크 부하 감소
EIP-7549Attestation 구조 단순화Attestation 구조 간소화(committee index를 서명 밖으로 이동), ZK 검증 및 클라이언트 효율 향상
EIP-6110입금 전달 방식 개선 (EL → CL)Deposit 정보를 실행 계층에서 직접 합의 계층으로 전달하는 방식 도입. 더 빠르고 안전한 입금 처리
EIP-7002EL 기반 밸리데이터 exit 지원실행 계층에서 밸리데이터 exit을 트리거할 수 있도록 하는 특별 컨트랙트 도입. 밸리데이터 키 분실 이슈 해결
  • 네트워크 및 프로토콜 인프라
EIP제목설명
EIP-7685EL ↔︎ CL 메시지 전송 표준화실행 계층에서 합의 계층으로 요청을 안전하게 전달할 수 있는 표준 프레임워크 정의. EL ↔︎ CL 연결 강화
EIP-2935블록 해시를 상태로 저장최근 8192개 블록 해시를 상태(state)에서 제공하는 시스템 컨트랙트 도입. Stateless execution 지원
  • Account Abstraction (AA)
EIP제목설명
EIP-7702EOA에 스마트 컨트랙트 코드 허용EOA에서 임시 스마트 컨트랙트 코드를 부여하여 EOA를 확장할 수 있도록 하는 새로운 트랜잭션 타입 도입
EIP-2537BLS12-381 연산 프리컴파일 도입BLS12-381 커브 연산을 위한 프리컴파일 추가로, AA 및 ZK/스테이킹 등에서 효율적인 서명 검증 가능
  • Blob (데이터 가용성 및 Rollup 지원)
EIP제목설명
EIP-7691블롭 수 증가블롭 수 조정(평균 3개 → 6개, 최대 6개 → 9개), 롤업 확장성 향상
EIP-7623calldata 비용 차등화calldata 가격을 데이터 무거운 트랜잭션만 인상 → 블롭 사용 유도 및 블록 크기 제한
EIP-7840블롭 스케줄을 EL 설정에 포함실행 계층 설정 파일에 블롭 스케줄(타겟/최대 수 등) 직접 설정 가능하도록 구조 개선

[EIP-7251] 스테이킹 상한 상향 (2048 ETH)

consensus layer의 밸리데이터의 스테이킹 한도를 높였다.

변경점

  • 이전에 32 ETH 였던 스테이킹 수량을 32~ 2048 ETH 범위에서 자유롭게 설정이 가능하도록 변경
  • 이로 인해 33 ETH를 스테이킹 하면 기존에는 32 ETH 까지만 보상이 적용되었지만, 이제는 추가 1 ETH도 보상 대상에 포함
  • 밸리데이터 역할을 유지하면서 스테이킹 물량 또는 보상만 일부 출금하는 기능 추가

효과

  • 기존에는 스테이킹 수량이 32ETH로 정해져 있었기 때문에 더 많은 ETH를 스테이킹 하려면 여러개의 밸리데이터를 운영해야 했지만 이제는 하나의 밸리데이터에 ETH를 몰아서 운영 가능
  • 이로 인해 서명 수가 줄어들고, 네트워크 부하 감소 → 확장성 개선
    2025.05.20
    ETH를 하나의 밸리데이터로 몰면서 밸리데이터의 수가 즐어드는 현황

[참고]https://beaconcha.in/

[EIP-7549] Attestation 구조 단순화

이더리움 비컨체인에서 합의는 밸리데이터들이 블록과 epoch에 대해 투표(Attestation)를 하는 방식이다.

하나의 투표는 아래와 같이 구성되어 있었다.

type AttestationData struct {
    Slot            Slot            // 투표가 발생한 슬롯 번호 (투표 시점)
    Index           CommitteeIndex // 검증자가 속한 위원회 인덱스
    BeaconBlockRoot Root           // 투표 대상 블록의 루트 해시
    Source          Checkpoint     // 이전 epoch의 체크포인트
    Target          Checkpoint     // 현재 epoch의 체크포인트
}
// 예시
AttestationData{
    Slot: 12345,
    Index: 3,  // 이 슬롯에서 4번째 위원회
    BeaconBlockRoot: 0xabc123..., // 블록 root
    Source: Checkpoint{
        Epoch: 385,
        Root: 0xdeadbeef...,
    },
    Target: Checkpoint{
        Epoch: 386,
        Root: 0xbeefcafe...,
    },
}
  • committeeIndex (어떤 위원회에서 나왔는지)가 포함되어 있었기 때문에, 동일한 투표 내용이라도 서로 다른 committee에서 발생한 경우 서명 해시가 달라져서 집계가 어려운 문제가 발생

변경점

type AttestationData struct {
    Slot            Slot
    Index           CommitteeIndex // 항상 0으로 설정
    BeaconBlockRoot Root
    Source          Checkpoint
    Target          Checkpoint
}

type Attestation struct {
    AggregationBits Bitlist
    Data            AttestationData
    Signature       BLSSignature
    CommitteeBits   Bitvector // 새로운 필드로, 각 위원회에 대한 집계 정보를 포함
}
  • committee index 값을 Attestation 메시지 외부로 분리
  • 이 값은 더 이상 서명 대상이 아님 → 검증과 집계가 더 쉬워짐
//최종 포함 예시

ExecutionPayload:
  ...
  attestations: List[Attestation]  # 여러 개 포함될 수 있음

효과

  • 집계 효율성 향상: 동일한 투표 내용을 가진 Attestation들을 보다 쉽게 집계할 수 있어, 검증자들의 서명 검증 비용이 감소
  • 블록 공간 최적화: 블록에 포함할 수 있는 Attestation 수가 증가하여, 블록 공간 활용도가 향상
  • ZK 회로 성능 개선: ZK 회로를 사용하는 클라이언트에서의 성능이 크게 향상
    → ZK 증명을 검증하는 스마트 컨트랙트 측 코드도 최적화되어, 블록체인 내에서의 검증 비용과 시간이 줄어듦

[EIP-6110] 입금 전달 방식 개선 (EL → CL)

이전에 밸리더이터의 입금은 eth1data poll 이라는 기능을 통해 Beacon 체인에서 처리되었다. 이는 Merge 이전 Beacon Chain과 실행 계층가 분리되어 있었을 때 되입된 구조이다.
이전 deposit life cycle

Beacon Chain이 실행 계층의 데이터를 주기적으로 polling 하면서 예치금(입금)정보, 블록 해시 등을 수동으로 가져와야 했고, PoW 체인의 리오그(re-org)를 고려해야 했기 때문에 복잡하고 유연하지 않았다.

보충 설명
PoW 체인에서는 블록이 뒤집힐 가능성이 있음.
예를 들어, A 블록에 입금 기록이 있었는데 리오그로 A가 무효가 되면 입금도 무효
→ PoW 블록의 finality를 확신하기 어려우므로 여러 블록을 확인해야 하고, 이중 처리 방지도 신경 써야 함
리오그(re-org)
PoW 체인은 여러 채굴자가 동시에 블록을 만들 수 있기 때문에, 동시 생성된 유효한 블록 2개 라고 한다면 네트워크는 잠시 동안 두 체인을 갖게 됨. 나중에 더 긴 체인이 생기면 블록체인은 더 긴 체인을 정식 체인으로 선택하고, 그 결과 더 짧았던 체인은 버려지고 무효 처리됨. 이 과정을 리오그(re-org)라고 함.
그래서 PoW에서는 finality가 중요한데, 블록이 생성된 것이 끝이 아니라, 보통 6개 이상 블록이 쌓여야 안전하다고 보는것도 이것 때문
Q. 머지 이후에는?
PoS에서는 finality 개념이 훨씬 강력함. 블록이 체크포인트를 통해 합의되면 절대 되돌릴 수 없음
→ 리오그 위험이 사실상 사라짐
체크포인트
Beacon Chain에서 특정 슬롯마다 설정되는 특별한 블록을 의미
→ 이 블록들은 합의 투표(Attestation)의 대상이 되며, 그 중 일부는 finalized될 수 있음
이더리움은 1슬롯 = 12초, 1 에포크 = 32 슬롯인데, 각 에폭의 첫 블록이 하나의 체크포인트
Q. 체크 포인트가 중요한 이유?
체크포인트는 밸리데이터들이 “이 시점까지는 다 동의 했어”라고 합의하는 기준점이 됨
밸리데이터는 직전 체크 포인트와 새로운 체크포인트를 연결하는 블록 체인 구간에 대해 투표를 함
이 투표가 일정 조건 이상 만족되면, 그 체크포인트는 justified 상태가 되고, 그 직전 justified 체크포인트가 finalized가 됨

  • Justified 체크포인트: 2/3 이상 밸리데이터가 투표하면 정당화됨
  • Finalized 체크포인트: 다음 에포크에서 또 2/3 이상 투표되면 최종화됨

기존 방식

  1. 실행 계층에 있는 DepositContract를 통해 처리
  2. Becaon Chain은 에폭마다 이 DepositContract를 풀링하여 새로운 입금 이벤트를 감지
  3. 이 데이터는 eth1data라는 구조체에 요약되어 담기고, 이를 통해 새로운 밸리데이터의 입금이 처리됨. (입금 처리시 지연 발생)
eth1data: {
    deposit_root: bytes32,    # 실행 계층에서 보고된 Merkle 루트
    deposit_count: uint64,    # 누적 입금 수
    block_hash: bytes32       # 해당 데이터를 포함한 eth1 블록의 헤더 hash
}

EIP-6110은 밸리데이터 입금 정보를 실행 계층에서 직접 처리하도록 변경하여, 블록 생성 시 입금 데이터를 명시적으로 포함시키는 방법을 도입했다. 기존의 합의 계층에서의 입금 처리 방식보다 효율적이고 결정적인 처리를 가능하게 한다.

변경점

  • 입금 데이터의 명시적 포함: 블록 생성 시, 실행 계층은 DepositContract에서 발생한 이벤트 로그를 파싱하여 입금 정보를 수집하고, 이를 ExecutionPayloaddeposits 필드에 명시적으로 포함
    이후에 합의 계층
def event_data_to_deposit_request(deposit_event_data) -> bytes:
    deposit_data = parse_deposit_data(deposit_event_data)
    pubkey = Bytes48(deposit_data[0])
    withdrawal_credentials = Bytes32(deposit_data[1])
    amount = deposit_data[2]   # 8 bytes uint64 LE
    signature = Bytes96(deposit_data[3])
    index = deposit_data[4]    # 8 bytes uint64 LE

    return pubkey + withdrawal_credentials + amount + signature + index
class ExecutionPayload(Container):
    parent_hash: Hash32
    fee_recipient: ExecutionAddress
    state_root: Root
    receipts_root: Root
    logs_bloom: Bytes[LOGS_BLOOM_BYTES]
    prev_randao: Bytes32
    block_number: uint64
    gas_limit: uint64
    gas_used: uint64
    timestamp: uint64
    extra_data: Bytes[MAX_EXTRA_DATA_BYTES]
    base_fee_per_gas: uint256
    block_hash: Hash32
    transactions: List[Bytes[MAX_BYTES_PER_TRANSACTION], MAX_TRANSACTIONS_PER_PAYLOAD]
    withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]  # EIP-4895
    blob_gas_used: uint64                                       # EIP-4844
    excess_blob_gas: uint64                                     # EIP-4844
    blob_kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK]  # EIP-4844
    deposits: List[Bytes[DEPOSIT_DATA_SIZE], MAX_DEPOSITS_PER_PAYLOAD]  # EIP-6110
    requests: List[Bytes[MAX_REQUEST_SIZE], MAX_REQUESTS_PER_PAYLOAD]   # EIP-7002, EIP-7251

DepositContract : 0x00000000219ab540356cBB839Cbe05303d7705Fa

[참고] beacon-chain.md

  • 합의 계층의 단순화: 합의 계층은 더 이상 입금 로그를 직접 파싱하지 않고, 실행 계층에서 전달된 deposits 필드를 통해 입금 정보를 처리

    	- 합의 계층은 Go Ethereum(클라이언트) 코드베이스가 아니라 Prysm, Lighthouse, Teku 등 별도의 클라이언트에서 처리
    
    	- 즉, Geth는 실행 계층 역할이고, EIP-6110에서 합의 계층의 입금 처리 코드는 Geth가 아닌 별도 Beacon 클라이언트(Go 언어로 작성된 Prysm 등)에서 구현
    
    	- 합의 계층 클라이언트는 로그를 파싱해 `DepositData` 생성
    
    	- 이를 Beacon state에 반영하여 밸리데이터 등록 또는 balance 업데이트
  • 결정성 및 효율성 향상: 입금 데이터가 블록에 명시적으로 포함되므로, 입금 처리의 결정성이 보장되며, 입금 처리 지연이 감소

실행 순서

  1. 실행 계층에서는 사용자가 입금(32 ETH 이상)을 수행하면 DepositEvent라는 이벤트 로그를 발생
  2. producer가 유저 tx를 실행
  3. 유저 tx가 모두 실행 후 system call(from을 system address로 한 호출) 호출해서 request 생성
  4. beacon이 engine_getPayloadV4 api를 호출해서 블록을 수신: Beacon Node(합의 계층 클라이언트)는 새로운 블록을 실행 계층으로부터 받을 때, 이 request들을 함께 전달받음
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
	log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
	data := api.localBlocks.get(payloadID, full)
	if data == nil {
		return nil, engine.UnknownPayload
	}
	return data, nil
}
type ExecutionPayloadEnvelope struct {
	ExecutionPayload *ExecutableData `json:"executionPayload"  gencodec:"required"`
	BlockValue       *big.Int        `json:"blockValue"  gencodec:"required"`
	BlobsBundle      *BlobsBundleV1  `json:"blobsBundle"`
	Requests         [][]byte        `json:"executionRequests"`
	Override         bool            `json:"shouldOverrideBuilder"`
	Witness          *hexutil.Bytes  `json:"witness,omitempty"`
}

[참고] engine_getPayloadV4

  1. 합의 계층 클라이언트는 이 request들 중 DepositEvent 로그를 찾아서,

  2. 로그 안의 pubkey, withdrawal_credentials, amount, signature, index 등을 파싱(해석)하고 이 정보를 가지고 DepositData 구조체(또는 객체)를 생성

    [참고] DepositRequest

  3. 생성한 DepositData는 Beacon Chain 상태(state)에 반영되어, 새로운 밸리데이터 등록 혹은 기존 밸리데이터의 잔액 업데이트 등에 사용

효과

  • 비컨체인이 굳이 실행 계층 데이터를 수동으로 확인하지 않아도 됨
  • 실시간 입금 처리 → 입금 대기 시간 단축
  • 머지된 이더리움에 맞는 구조로 리팩토링
  • 실행 계층의 히스토리가 만료되어도(예: Verkle 트리, state expiry) 문제없이 작동 가능 → 노드가 과거 입금 기록 없이도 부팅 가능

실행 계층의 히스토리가 만료된다”는 것은, 이더리움의 미래 로드맵에서 상태 및 기록 데이터를 오래 보관하지 않고 제거하겠다는 계획을 의미. 그 예시가 Verkle 트리와 state expiry

Verkle 트리 요약
Verkle 트리는 현재 이더리움에서 사용하는 Merkle Patricia Trie를 더 압축되고 효율적인 데이터 구조로 대체하기 위한 기술

  • 더 적은 증명 데이터로 상태 증명을 가능하게 함 (light client 친화적)
  • 상태 데이터를 압축 → 스토리지 사용 줄이기
  • 상태 만료(state expiry) 기능을 도입할 기반 제공

State expiry 요약
state expiry는 이더리움 실행 계층에 있는 계정, 컨트랙트, 스토리지 등의 상태 데이터(state)를 일정 시간이 지나면 만료시켜 더 이상 보관하지 않도록 하는 기능
예시)

  • 어떤 계정이 1년 동안 트랜잭션을 안 하면 상태에서 제거됨
  • 과거의 logs, receipts, calldata, storage 등도 일정 시간이 지나면 지워짐
  • 새로운 노드를 부팅해도 과거의 상태를 전부 보존할 필요 없음

[EIP-7002] EL 기반 validator exit 지원

이전에는 밸리데이터가 밸리데이터 탈퇴를 위해 출금(exit)을 하려면 BLS 서명 키(활성 키)가 반드시 필요했다.( → 반드시 BLS 활성 키를 이용해 서명한 exit message를 보내야 했음) 반면, 출금 수신 구조는 콜드키(비활성 키)로, 출금시 수령만 가능하고 직접 요청은 불가했다. 즉, 출금 트리거는 오직 활성 키로만 가능했기 때문에 키 분리 운영, 키 분실 또는 제 3자가 운영하는 구조에서 유연성이 부족했다.

기존 방식

  1. 출금 요청(Exit)은 합의 계층에서만 가능

    • 밸리데이터는 자신의 활성 BLS 서명키로 출금 요청 메시지(Exit message)를 만들어 비컨 체인에 제출
    • 즉, 활성키가 있어야 출금의 트리거를 만들 수 있음
  2. 비컨 체인에서해당 출금 정보가 상태(state)에 반영되고, 비컨 체인이 process_withdrawals를 통해 withdrawal 부분을 채우고 engine_NewPayloadV4 api를 호출하여 실행 계층으로 ExecutableData 데이터 전달

[참고] process_withdrawals

[참고] prepare_execution_payload

  1. 실행계층은 ExecutableData와 다른 데이터를 조합하여 새로운 블록에 들어갈 데이터를 만들고, 프로듀서는 tx를 실행하면서 요청 처리(withdrawal처리) 후 블록을 생성

  2. 생성된 블록은 비컨체인에서 다시 합의를 거쳐 확정

[참고] body.Withdrawals

반면, EIP-7002는 Withdrawal credential(콜드 키)로 실행 계층의 시스템 컨트랙트를 호출해서 validator exit를 트리거 할 수 있게 되었다.

  • Validator Exit Utility : 0x0000000000000000000000000000000000005001
//Withdrawal credential 예시
0x010000000000000000000000b794f5ea0ba39494ce839613fffba74279579268
  • 밸리데이터가 탈퇴할 때 ETH를 수령할 주소를 지정하는 32바이트 값
  • 형식 : 0x01 + 11바이트 0-padding + 20바이트 주소 (즉, 0x01로 시작 → 실행 계층 주소임을 의미)

변경점

  • 실행 계층에서 동작하는 스마트 컨트랙트(시스템 컨트랙트)를 도입
    [참고] SystemContracts
  • 해당 컨트랙트를 호출하면, 컨트랙트는 밸리데이터의 withdrawal credential을 확인하여 일치하면 exit이 요청을 deposit-request 처리방식으로 처리하고 비컨 체인에 전달
  • 비컨 체인에 직접 접근하지 않아도 되고, 서명키 없이 출금 트리거가 가능

실행 순서

  1. 밸리데이터가 withdrawal credential로 등록한 주소로 시스템 컨트랙트에 add_withdrawal_request(pubkey, amount) 같은 함수 tx를 전송
//https://eips.ethereum.org/EIPS/eip-7002#add-withdrawal-request

def add_withdrawal_request(Bytes48: validator_pubkey, uint64: amount):
    """
    Add withdrawal request adds new request to the withdrawal request queue, so long as a sufficient fee is provided.
    """

    # Verify sufficient fee was provided.
    fee = get_fee()
    require(msg.value >= fee, 'Insufficient value for fee')

    # Increment withdrawal request count.
    count = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT)
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, count + 1)

    # Insert into queue.
    queue_tail_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    queue_storage_slot = WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 3
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, validator_pubkey[0:32])
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, validator_pubkey[32:48] ++ uint64_to_little_endian(amount))
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
    
  1. 실행 계층에서 프로듀서가 tx를 실행시키고 시스템 컨트랙트를 콜해서 request를 생성
  2. request를 engine_getPayloadV4 api를 통해 beacon에 전달 : Beacon Node(합의 계층 클라이언트)는 새로운 블록을 실행 계층으로부터 받을 때, 이 request들을 함께 전달받고 deposit-request처럼 처리하고 다시 Withdrawal이 실행계층에서 처리

[참고] Withdrawal

  1. 이후, 일정 시간 대기 후 이더리움 체인에서 최종 출금 처리

효과

  • 밸리테이터 키와 출금 키의 분리 운영이 용이
  • 노드 운영자에 대한 신뢰 의존도가 감소
  • 스테이킹 프로토콜의 유연성과 보안성이 향상

[EIP-7685] EL ↔ CL 메시지 전송 표준화

EIP-6110(검증자 예치금 온체인 처리) 및 EIP-7002(실행 계층에서의 검증자 출금 트리거)와 같은 제안들과 함께 작동하여 실행 계층과 합의 계층 간의 통신을 개선한다.

EIP-7685는 실행 계층에서 발생한 다양한 요청을 합의 계층으로 효율적으로 전달하고 처리할 수 있는 프레임워크를 제공하여 이더리움의 모듈화 및 확장성을 향상시킨다.

실행 계층의 블록 헤더에 새로운 32바이트 필드인 requests_hash가 추가되었다. 이 필드는 블록 내의 모든 요청을 해시화하여 생성된 값으로, 합의 계층이 해당 요청들을 검증하고 처리하는 데 사용한다.

합의 계층은 requests_hash를 통해 실행 계층에서 전달된 요청들을 인식하고, 각 요청의 request_type에 따라 적절한 처리를 수행한다. (예 : 밸리데이터의 exit 요청, deposit 요청)

//go-ethereum/miner/worke.go

func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult {
	//블록 헤더, 상태 트리 등 초기 작업 환경 구성
	work, err := miner.prepareWork(params, witness)
	if err != nil {
		return &newPayloadResult{err: err}
	}
	
	// 블록에 포함될 트랜잭션을 메모리풀에서 가져와 정렬 및 필터링하여 포함
	if !params.noTxs {
		interrupt := new(atomic.Int32)
		timer := time.AfterFunc(miner.config.Recommit, func() {
			interrupt.Store(commitInterruptTimeout)
		})
		defer timer.Stop()

		err := miner.fillTransactions(interrupt, work)
		if errors.Is(err, errBlockInterruptedByTimeout) {
			log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit))
		}
	}
  
    // 실행 결과로 발생한 Withdrawals 포함 및 로그 수집
	body := types.Body{Transactions: work.txs, Withdrawals: params.withdrawals}
	allLogs := make([]*types.Log, 0)
	for _, r := range work.receipts {
		allLogs = append(allLogs, r.Logs...)
	}

	// Collect consensus-layer requests if Prague is enabled.
	// (Prague 하드포크 이후) 합의 계층 요청 수집
	var requests [][]byte
	if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
		requests = [][]byte{}
		
		// Deposit 로그를 수집하는 부분
		// EIP-6110 : DepositContract 로그를 파싱하여, 입금 요청을 requests 배열에 추가
		if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig); err != nil {
			return &newPayloadResult{err: err}
		}
		// EIP-7002 : EL withdrawal credentials 기반으로 validator exit을 요청
		if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
			return &newPayloadResult{err: err}
		}
		// EIP-7251 : 여러 validator를 합치는 consolidation 요청 처리
		if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
			return &newPayloadResult{err: err}
		}
		// -> 이 요청들은 합의 계층이 처리해야 할 작업 목록으로, requests로 담아서 블록에 포함
	}
	
	// 요청 목록을 해시화 하여 블록 헤더에 포함
	if requests != nil {
		reqHash := types.CalcRequestsHash(requests)
		work.header.RequestsHash = &reqHash
	}
	
    //최종 블록 조립
	block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts)
	if err != nil {
		return &newPayloadResult{err: err}
	}
	
	//결과 반환
	return &newPayloadResult{
		block:    block,
		fees:     totalFees(block, work.receipts),
		sidecars: work.sidecars,
		stateDB:  work.state,
		receipts: work.receipts,
		requests: requests,
		witness:  work.witness,
	}
}

[참고] worker.go

[참고] processRequestsSystemCall

[참고] SystemContracts

변경점

  • 요청 구조 : 각 요청은 request_type(1바이트)과 request_data로 구성되며, request_data는 해당 요청의 세부 정보를 포함하는 불투명한 바이트 배열
  • 블록 헤더 확장 : 실행 계층의 블록 헤더에 requests_hash 필드를 추가하여, 블록 내 모든 요청의 커밋먼트를 저장. 이 해시는 요청들을 해시한 값들의 리스트를 다시 해시하여 계산
  • 합의 계층 처리 : requests_hash를 통해 합의 계층은 실행 계층에서 발생한 요청들을 확인하고 적절한 시점에 처리

실행 순서


1. (CL → EL) engine_forkchoiceUpdatedV3 호출 : 새로운 블록 상태와 타임 정보를 EL에 전달
2. (EL) 내부적으로 preparePayloadV3 호출 : ExecutionPayload 준비 (deposit, requests, withdrawals, transactions 등 조립)
3. (CL-> EL) engine_getPayloadV4(payloadID) 호출 : 생성된 ExecutionPayload를 요청
4. (EL → CL) returns ExecutionPayload : transactions, withdrawals, deposits, requests, blobsBundle, parentBeaconBlockRoot 등을 포함한 완전한 Payload 반환.
5. (CL) Beacon Block 구성 : 이 ExecutionPayload를 Beacon block에 포함하여 최종 블록 구성
6. (CL → EL) engine_newPayloadV4(payload) 호출 : 해당 ExecutionPayload를 실제로 실행하여 블록을 적용하도록 요청. 유효성 검증 후 결과 반환

[참고] api.go

효과

  • 유연한 확장성
    새로운 요청 유형을 도입할 때 실행 계층의 블록 구조를 변경할 필요 없이, 다양한 요청을 지원
  • 스마트 컨트랙트 기반 검증자 지원
    스마트 컨트랙트가 검증자의 관리 및 운영을 담당하는 경우, 실행 계층에서 발생한 요청을 통해 합의 계층과의 상호작용이 가능
  • 프로토콜 단순화
    요청의 세부 사항을 불투명한 바이트 배열로 처리함으로써, 다양한 인코딩 형식을 지원하고 프로토콜의 복잡성을 줄임

[EIP-2935] 블록 해시를 상태로 저장

기존 EVM에서는 BLOCKHASHopcode를 통해 스마트 컨트랙트에서 블록 해시를 조회할 수 있었다. 하지만 최신 256개 블록까지만 조회가 가능(BLOCKHASH(n))했고, 이는 앞으로 stateless client(상태를 저장하지 않는 클라이언트) 환경에서는 불편하거나 불가능해질 수 있다.

8192개인 이유?
1. 롤업(rollup) & light client 사용 시나리오 최적화

  • 롤업 시스템은 종종 몇 천 블록 전 데이터를 증명으로 제출함.
  • 예: 12초 블록 * 8192 = 약 1.1일 분량의 블록 해시 보존.
  • 즉, 약 1~1.2일 분량의 롤업 증명 처리에 필요한 블록 해시 커버 가능.
    2. 트레이드오프 고려
  • 블록마다 32바이트 해시 × 8192개 = 약 256 KiB 추가 데이터.
  • 클라이언트가 관리하기 부담 없는 크기로 설계 (RAM/디스크 부담 고려).
  • 특히 컨센서스 레이어가 아닌 실행 레이어에 저장되는 것이므로 효율성 중요.
    3. 2의 거듭제곱 값 사용
  • 8192 = 2¹³
  • 내부 처리 (슬라이딩 윈도우, 링버퍼 구조 등)에서 bitmaskindex 연산 최적화가 가능.
  • 가변 크기 배열보다 고정된 슬라이딩 윈도우 구조가 성능과 단순성 측면에서 유리.

변경점

  • 과거 8192개 블록 해시를 조회할 수 있는 시스템 컨트랙트 도입
  • 이 블록 해시들은 컨트랙트의 스토리지 슬롯에 저장
  • 스마트 컨트랙트는 이 시스템 컨트랙트를 통해 직접 긴 기간의 블록 해시를 조회할 수 있음
ParameterValue
BLOCKHASH_SERVE_WINDOW256
HISTORY_SERVE_WINDOW8191
SYSTEM_ADDRESS0xfffffffffffffffffffffffffffffffffffffffe
HISTORY_STORAGE_ADDRESS0x0000F90827F1C53a10cb7A02335B175320002935
  • BLOCKHASH_SERVE_WINDOW : BLOCKHASH오피 코드로 조회 가능한 과거 블록 해시의 최대 개수
  • HISTORY_SERVE_WINDOW : 시스템 컨트랙트를 통해 저장되는 과거 블록 해시의 개수
  • SYSTEM_ADDRESS : 시스템 컨트랙트가 트리거 되는 특수 주소. 이 주소에서 실행되는 코드나 호출은 “시스템 수준”으로 간주
  • HISTORY_STORAGE_ADDRESS : 과거 블록 해시를 저장하고 조회하는 시스템 컨트랙트 주소. 일반 트랜잭션 및 컨트랙트에서 CALL 가능
  • set(bytes32 blockHash) : SYSTEM_ADDRESS만 호출 가능. 블록 해시 저장
  • get(uint256 blockNumber)bytes32 :누구나 호출 가능. 저장된 과거 해시 반환

흐름

[블록 처리 중]

  1. SYSTEM_ADDRESS (자동 실행)
  2. HISTORY_STORAGE_ADDRESS.set(block.parentHash)
  3. 내부 링 버퍼에 저장 (최대 8191개)
    → 이후 사용자나 스마트컨트랙트는 HISTORY_STORAGE_ADDRESS.get(n)을 통해 과거 블록 해시를 조회

[참고] execution_hash

효과

  • Stateless 실행 구조에 대비한 준비
    향후 stateless 클라이언트는 상태 검증 시, 과거 블록 해시에 대한 접근이 필요할 때가 많은데, 기존 한정된 256 블록 범위로는 부족
  • 향후 도입될 Verkle 트리 구조와도 호환
  • 롤업(Rollups) 등은 지금 당장 더 넓은 범위의 블록 해시를 조회 가능해져 유용함

이더리움 상태(state)란?
계정의 잔액, 스마트 컨트랙트의 스토리지, nonce 등 모든 온체인 데이터
현재 이더리움 노드는 전체 상태를 메모리에 보관하여 트랜잭션을 처리
Stateless Ethereum이란?
노드가 전체 상태를 저장하지 않고도 트랜잭션을 처리할 수 있게 하려는 연구 방향

  • 트랜잭션이 “필요한 상태 데이터”를 함께 포함
    트랜잭션에, 그것이 접근하거나 변경할 계정/스토리지에 대한 상태 증명(예: Merkle proof등)을 같이 포함시킴
    따라서, 검증자나 노드가 별도로 상태를 보유하지 않아도 해당 트랜잭션이 유효한지 검증 가능
  • 상태 저장 노드와 stateless 노드를 분리
    일부 노드는 상태를 저장(archival/full nodes)
    다른 노드는 상태 없이 검증만 수행(stateless clients)
    Stateless 필요성
  1. 노드 운영 비용 감소
    전체 상태를 저장하지 않으면, 하드웨어 요구사항이 낮아짐 → 더 많은 노드 참여 유도
  2. 동기화 문제 완화
    신규 노드가 전체 상태를 받지 않아도 바로 네트워크에 참여 가능
  3. 더 빠른 트랜잭션 처리
    일부 상황에선 경량화된 검증으로 처리 속도 증가
    Stateless 클라이언트가 제대로 동작하려면?
  4. 현재 상태에 대한 효율적인 증명이 필요하고 ( → Verkle Tree)
  5. 과거 블록 해시에 대한 접근 및 검증이 필요 ( → EIP-2935)
    관련 기술
  • witness (증인 데이터) : 트랜잭션 실행에 필요한 상태 데이터와 그 Merkle proof
  • Verkle Tree : 기존 Merkle Patricia Tree 보다 작은 proof로 상태 검증을 가능하게 하는 새로운 상태 트리 구조(EIP-4844 이후 적용 예상)
  • EIP-4444 : 오래된 상태(history)를 보존할 필요가 없도록 해주는 프로토콜

[EIP-7702] EOA에 스마트 컨트랙트 코드 허용

EIP-7702는 AA를 구현하기 위한 핵심 제안 중 하나로, EOA를 일시적으로 스마트 컨트랙트처럼 동작하게 만드는 기능을 도입한다. 이는 기존의 EOA와 스마트 컨트랙트의 경계를 허물고 유연성과 확장성을 높이기 위한 기반을 제공한다.

변경점

  • 기존에는 계정(Account)이 EOA(개인 키로 서명하는 계정)와 Contract Account(CA, 스마트 컨트랙트)으로 구분되었는데, EIP-7702는 EOA가 트랜잭션 중 임시적으로 코드를 가지는 것을 허용
  • 즉, 지갑 주소가 트랜잭션 중에만 컨트랙트처럼 특정 로직을 수행할 수 있게 됨

작동 원리

새로운 트랜잭션 필드 도입
EIP-7702는 새로운 트랜잭션 타입(0x04)에 다음 필드를 추가한다.

  • authorizationList : [[chain_id, address, nonce, y_parity, r, s], ...]

EIP-7702 작성 초기에는 delegated될 컨트랙트의 코드를 직접 authorization list에 추가했으나 최종적으로는 tx 크기를 줄이기 위해 컨트랙트의 address를 추가하기로 결정 (자세한 이유는 섹션 참고)

Creation by template
The cost analysis makes the answer clear. The smallest proxy would be around 50 bytes and an address is 20 bytes. The 30 byte difference provides no useful additional functionality and will be inefficiently replicated billions of times on the chain.

이 트랜잭션을 실행할 때만 이 코드를 임시로 계정에 붙여서 동작한다. 실행이 끝나면 코드는 사라지고 원래 EOA로 돌아간다.

[구조체 예시]

[참고] transaction.go

// Transaction types.
const (
	LegacyTxType     = 0x00
	AccessListTxType = 0x01
	DynamicFeeTxType = 0x02
	BlobTxType       = 0x03
	SetCodeTxType    = 0x04
)

[참고] tx_setcode.go

type SetCodeTx struct {
	ChainID    *uint256.Int
	Nonce      uint64
	GasTipCap  *uint256.Int // a.k.a. maxPriorityFeePerGas
	GasFeeCap  *uint256.Int // a.k.a. maxFeePerGas
	Gas        uint64
	To         common.Address
	Value      *uint256.Int
	Data       []byte
	AccessList AccessList
	AuthList   []SetCodeAuthorization

	// Signature values
	V *uint256.Int
	R *uint256.Int
	S *uint256.Int
}

type SetCodeAuthorization struct {
	ChainID uint256.Int    `json:"chainId" gencodec:"required"`
	Address common.Address `json:"address" gencodec:"required"`
	Nonce   uint64         `json:"nonce" gencodec:"required"`
	V       uint8          `json:"yParity" gencodec:"required"`
	R       uint256.Int    `json:"r" gencodec:"required"`
	S       uint256.Int    `json:"s" gencodec:"required"`
}
필드설명
chainId이 서명이 유효한 체인 ID
Address위임된 실행 대상 주소 (컨트랙트 주소 등), 예: `0xef0100address`
nonce서명자의 nonce (재사용 방지)
VBLS/ECDSA 서명값 Y-parity
R서명값 R
S서명값 S

[참고] statedb.go

setCode를 사용하여 새로운 코드를 설정. 이때 코드의 해시도 함께 계산하여 저장

func (s *StateDB) SetCode(addr common.Address, code []byte) (prev []byte) {
	stateObject := s.getOrNewStateObject(addr)
	if stateObject != nil {
		return stateObject.SetCode(crypto.Keccak256Hash(code), code)
	}
	return nil
}

이때, address 기준으로 코드가 저장되기때문에 같은 address에 대하여 다른 코드가 들어가 있다면 덮어써질 수 있음

SetCodeAuthorization[] = [
  { account: 0xAlice, code: CODE_A, signature: SIG_A },
  { account: 0xAlice, code: CODE_B, signature: SIG_B }
]

이 경우 실행 흐름
1. 첫 번째 setCode(0xAlice, CODE_A) 적용
2. 두 번째 setCode(0xAlice, CODE_B)로 덮어씀
3. 실행 시에는 CODE_B만 적용됨

이 트랜잭션은 EOA가 직접 임시로 특정 스마트 컨트랙트 코드를 실행할 권한을 AuthList를 통해 부여받아 트랜잭션을 수행할 수 있도록 한다.

AuthList는 일종의 위임 서명 모음이며, 검증된 서명이 있으면 해당 주소의 코드를 실행하는 것이 허용된다.

즉, 기존 EOA는 여전히 단순 서명 기반으로 작동되지만, 이 새로운 트랜잭션 유형을 사용하면 EOA가 마치 컨트랙트처럼 확장된 기능을 실행할 수 있는 것이다.

트랜잭션 처리 흐름

  1. 트랜잭션 수신 및 디코딩
    • Geth는 새로운 트랜잭션 유형(Type 0x04)을 수신하면, 이를 SetCodeTx 구조체로 디코딩
    • 이 구조체는 authorizationList 필드를 포함하여, EOA가 위임한 스마트 계약 주소와 서명 정보를 포함
  2. 서명 검증 및 권한 확인
    SetCodeAuthorization 항목에 대해 다음을 수행
    • 서명 유효성 검증: ecrecover를 사용하여 서명자가 실제 EOA인지 확인합니다.
    • 체인 ID 및 nonce 확인: 트랜잭션이 현재 체인에서 유효하며, 재사용되지 않았는지 확인
    • 위임 주소의 코드 상태 확인: 해당 주소가 비어 있거나(EOA) 이미 위임된 상태인지 확인(위임된 EOA)
  3. 위임 코드 설정
    • 검증이 완료되면, EOA의 코드 필드를 0xef0100 || address 형식으로 설정하여, 지정된 스마트 컨트랙트 주소로의 위임임을 표시
    • 이 설정은 트랜잭션 실행 중에만 유효하며, 트랜잭션 종료 후에는 원래 상태로 복원
  4. 트랜잭션 실행
    • 위임된 코드가 설정된 EOA는 스마트 컨트랙트처럼 동작할 수 있으며, 트랜잭션의 data 필드에 지정된 로직을 실행
    • 이 과정에서 EOA는 스마트 컨트랙트의 기능을 활용하여 다양한 작업을 수행할 수 있습니다.
  5. 트랜잭션 종료 및 상태 복원
    • 트랜잭션 실행이 완료되면, EOA의 코드 필드는 원래 상태로 복원되며, 위임된 코드 설정은 제거
    • 이로써 EOA는 다시 일반 계정으로 회귀

[트랜잭션 예시]

Etherscan에서 set code tx 조회 가능https://etherscan.io/txnauthlist

트랜잭션 실행 흐름

  1. EOA는 authorizationList에 서명된 임시 코드를 포함시켜 트랜잭션을 보낸다.
  2. 검증 시, EOA가 서명한 accountCode가 해당 트랜잭션 동안 임시 코드로 계정에 로드된다.
  3. 트랜잭션 실행이 완료되면, 다시 원래의 코드 없음 상태 (EOA)로 돌간다.

즉, 트랜잭션 중에만 스마트 계약처럼 동작하는 EOA를 만들 수 있는 것이다.

tx 호출 시 sender 뿐만 아니라 authority의 nonce도 1 증가함
따라서 sender 주소, to 주소, authority 주소가 모두 같고 authorization_list 길이가 1이면 nonce가 최종적으로 2 증가함 (tx 예시: tx의 nonce는 1, authorization_list 안의 nonce는 2)

✔️ EOA vs EIP-4337 vs EIP-7702

항목EOAEIP-4337EIP-7702
서명 방식단일 개인 키 서명커스텀 검증 함수트랜잭션 내 임시코드로 검증
계정 유형외부 소유 계정(EOA)스마트 컨트랙트 계정(CA)EOA + 임시 컨트랙트 기능
컨트랙트 사용없음필수트랜잭션 중에만 임시 사용
멀티시그 지갑불가능가능가능
소셜 복구불가능가능가능
가스 수수료 커스터마이징불가능수수료 대납 가능유연한 처리 가능
Layer 2 호환성기본 지원추가 구현 필요유연한 지원
도입 난이도없음높음(entry point 필요)낮음 (EOA 확장)
배포 필요 여부없음컨트랙트 배포 필요없음
트랜잭션 타입Legacy / EIP-1559UserOperationEIP-7702(새로운 타입)
사용자 지갑 변화단순 지갑완전히 새로운 컨트랙트 지갑 필요기존 지갑 그대로 사용하며 확장 가능
목표 및 철학단순성과 직접 서명완전한 계정 추상화 구조 구현기존 EOA를 확장해 추상화에 근접
요약단순하고 보편적이지만 기능이 제한적강력하지만 복잡하고 별도 인프라 필요EOA를 그대로 유지하면서 컨트랙트 기능을 임시로 부여

활용 예시 1: 스마트 지갑처럼 서명 검증 방식 바꾸기

상황

  • Alice는 기존 EOA(0xAlice)를 사용하지만, 계정 키 분실 방지를 위해 다중 서명으로 보호받고 싶어 함
  • 스마트 지갑은 이를 지원하지만, Alice는 기존 EOA를 유지하고 싶음

해결 방법 (EIP-7702)
1. Alice는 트랜잭션을 보낼 때 다음을 포함함:
authList에 자신의 계정(0xAlice)에 다음 코드 지정:

function validateUserOp(bytes calldata userOp) public view returns (bool) {
    return verifyMultisig(userOp); // 3-of-5 방식 등
}
  1. 이 트랜잭션 하나에 한해, Alice의 계정은 이 멀티시그 검증 코드로 작동
  2. 트랜잭션이 끝나면 계정은 다시 코드 없는 EOA로 돌아감

장점

  • 기존 키 변경 없이 스마트 지갑의 일부 기능을 EOA에서도 사용
  • "EOA인데 스마트 지갑처럼 동작" 가능

활용 예시 2: EOA 계정으로 Paymaster 기능 수행

상황

  • Alice는 수수료 없이 DeFi 서비스를 사용하고 싶음
  • Paymaster(가스 대납자) 계약을 사용하면 가능하지만, 계정이 EOA라서 안 됨

해결 방법

  • Alice는 트랜잭션에 authList를 통해 다음 코드를 지정:
function validatePaymaster(bytes calldata userOp) external returns (bool) {
    return signatureCheck(userOp);
}
  • 이 트랜잭션에서는 Alice의 계정이 paymaster 검증 코드를 가진 컨트랙트처럼 동작
  • 수수료는 지정된 컨트랙트가 대신 지불

활용 예시 3: EOA로 번들 트랜잭션 처리 (Batch execution)

기존 방식

  • EOA는 한 번에 하나의 트랜잭션만 가능
    → 컨트랙트 계정은 여러 작업을 하나의 트랜잭션에 번들로 처리 가능

해결 방법

  • EOA가 아래와 같은 코드를 authList에 담아서 실행:
function executeBatch(bytes[] calldata calls) external {
    for (uint i = 0; i < calls.length; i++) {
        (bool success, ) = address(this).call(calls[i]);
        require(success);
    }
}
  • 이 트랜잭션 하나에서 EOA는 다중 작업을 한 번에 처리할 수 있음

[EIP-2537] BLS12-381 연산 프리컴파일 도입

BLS12-381은 이더리움 PoS 합의 계층에서 밸리데이터 서명에 사용되는 곡선 연산이다.

EIP-2537 도입 전에는 이더리움에서 BLS12-381 연산을 직접적으로 지원하지 않았기 때문에, 개발자나 프로토콜 레벨에서 해당 연산을 사용할 때 직접 구현하거나 외부 라이브러리에 의존해야 했다.

특히, 연산 자체가 복잡하고 무겁기 때문에 Solidity 등에서 직접 구현할 수 없어 클라이언트(Go, Rust 등)에서 구현했다.

프리 컴파일

  • EVM에 내장된 특수 스마트 컨트랙트
  • 사용자가 배포하는게 아니라, 클라이언트(Geth 등)에 직접 구현됨
  • 일반 스마트 컨트랙트처럼 호출 가능하지만, 성능과 보안이 뛰어남
  • 주로 암호 연산과 같은 고성능 작업을 위해 사용

변경점

  • BLS12-381 곡선 연산을 지원하는 프리컴파일 도입
  • BLS12-381 연산을 빠르게 실행할 수 있는 precompile을 제공bn256 처럼 시스템 컨트랙트 주소로 호출 가능
  • 해당 프리컴파일을 통해 이 곡선을 이용한 암호 연산(예 : 서명 검증 등)을 효율적으로 수행 가능

효과

  • 개발자들이 EVM 내에서 쉽게 BLS12-381 연산 수행 가능
  • 기존에 커스텀 스마트 컨트랙트로 구현하던 연산보다 가스비 절감, 보안 강화
  • 다음과 같은 온체인 애플리케이션의 성능 향상
    - 스테이킹 풀
    - 리스테이킹
    - 라이트 클라이언트
    - 브릿지
    - 영지식 관련 애플리케이션

[EIP-7691] 블롭 수 증가

Blob
EIP-4844은 이더리움에 새로운 트랜잭션 유형을 추가하여 Blob 데이터를 담을 수 있도록 함
이 블롭은 execution layer에서 직접 읽히지 않으며, 대신 data availability layer를 통해 Rollup 확장성을 높이는데 쓰임

// BlobTx represents an EIP-4844 transaction.
type BlobTx struct {
	ChainID    *uint256.Int
	Nonce      uint64
	GasTipCap  *uint256.Int // a.k.a. maxPriorityFeePerGas
	GasFeeCap  *uint256.Int // a.k.a. maxFeePerGas
	Gas        uint64
	To         common.Address
	Value      *uint256.Int
	Data       []byte
	AccessList AccessList
	BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas
	BlobHashes []common.Hash
	// A blob transaction can optionally contain blobs. This field must be set when BlobTx
	// is used to create a transaction for signing.
	Sidecar *BlobTxSidecar `rlp:"-"`
	// Signature values
	V *uint256.Int
	R *uint256.Int
	S *uint256.Int
}
// BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct {
	Blobs       []kzg4844.Blob       // Blobs needed by the blob pool
	Commitments []kzg4844.Commitment // Commitments needed by the blob pool
	Proofs      []kzg4844.Proof      // Proofs needed by the blob pool
}

흐름 간단 요약
1. blobHash / blob / proof 등을 blobTx에 담아서 tx를 만들어서 txPool로 전송
2. EL에서 블록으로 생성
3. CL에서 getPayload를 통해서 blob을 가져감
4. CL에서 blobHash와 proof를 통해 블롭의 유효성을 검증하고 블록을 합의 ( → 이때 합의를 위해 blob은 randao로 결정되는 P2P subnet을 통해서 CL 노드간 blob 전파)
5. EL에서는 CL에서 받은 blobHash와 유저가 tx를 만들때 계산 했던 blobHash와 같은건지 확인하고 저장
6. layer2에서는 이 블롭 데이터를 활용하여 트랜잭션 데이터를 게시하거나 증명을 제공

Blob은 레이어2를 위한 데이터 가용성을 제공하는 구조로, 이전 덴쿤(Dencun) 업그레이드에서 도입되었다.

기존 EIP-4844의 문제점

항목내용
고정 목표 블롭 수블록당 target = 3 blobs (최대 6 blobs)
수수료 조절 한계blob\_base\_fee는 블롭 수 증가에 따라 선형 조절되지만 경직된 구조
과도한 수요 발생 시수수료 급등 → 블롭을 넣기 어려움 → 데이터 포스팅 비용이 치솟음
유연성 부족L2나 Rollup 수요 급증 시 적응이 느림

변경점

  • 이전에는 블록당 평균 3개, 최대 6개의 블롭을 처리했지만, EIP-7691 적용 후 블록당 평균 6개, 최대 9개의 블롭 처리가 가능
  • 결과적으로 이더리움 롤업(L2)의 데이터 처리 용량이 증가

이 EIP-7691의 도입은, EIP7594(PeerDAS)가 도입되어 더 높은 블롭 처리량을 지원하기 전까지 과도기적 확장 솔루션 역할을 수행

효과

  • 블롭 처리량을 수요 기반으로 동적으로 확장 가능하게 변경
    target_blobs_per_block : 3 → 6
    max_blobs_per_block : 6 → 9
// DefaultCancunBlobConfig is the default blob configuration for the Cancun fork.
DefaultCancunBlobConfig = &BlobConfig{
	Target:         3,
	Max:            6,
	UpdateFraction: 3338477,
}
	
// DefaultPragueBlobConfig is the default blob configuration for the Prague fork.
DefaultPragueBlobConfig = &BlobConfig{
	Target:         6,
	Max:            9,
	UpdateFraction: 5007716,
}
  • 블롭 수수료(blob_base_fee) 곡선 개선
    - 기존 EIP-1559 스타일은 수수료 급등이 너무 빠름
    - EIP-7691은 수수료 증가를 완만하게 조정

    • EIP-7691은 수수료 증가를 완만하게 조정
      • 더 많은 사용자가 감당 가능
      • Rollup/L2 들이 더 자주 blob을 포스팅 가능
      • UpdateFraction
        - 블롭 수수료를 조정할 때, 이전 블록의 볼롭 사용량과 목표 블롭 수(target_blobs_per_block)의 차이를 기반으로 수수료를 증가시키거나 감소시키는데, 이때 UpdateFraction은 수수료 조정의 속도를 결정하는 분모로 사용
        • 블롭이 가득 찬 경우 수수료가 약 8.2% 증가, 블롭이 비어있는 경우 수수료가 약 14.5% 감소하도록 조정
  • Rollup 친화적 구조
    - Rollup의 데이터 수요는 갑작스럽게 증가할 수 있음
    - 이를 위해 L2-friendly fee tuning을 도입
    - L2가 안정적으로 Data availability를 확보할 수 있게 함

수수료 조정 공식 (EIP-4844 & 7691 동일)
new_base_fee = old_base_fee × (1 + Δ / update_fraction)

  • Δ = excess_blob_gas - target_blob_gas
  • update_fraction = 5007716 (EIP-7691의 UpdateFraction)
  • target_blob_gas = target_blobs × BLOB_GAS_PER_BLOB
    (하나의 blob gas는 131072)

블롭이 가득 찬 경우 (9개 → full usage)

  • Target: 6개 × 131072 = 786432
  • Actual: 9개 × 131072 = 1179648
  • Δ = 1179648 - 786432 = 393216
base_fee_new = base_fee_old × (1 + Δ / update_fraction)
             = base_fee_old × (1 + 393216 / 5007716)
             ≈ base_fee_old × 1.0785 → 약 7.85%

✔ 공식 문서에서는 근사값으로 약 8.2%라고 표현

블롭이 비어 있는 경우 (0개)

  • Actual = 0, Target = 786432
  • Δ = 0 - 786432 = -786432
base_fee_new = base_fee_old × (1 + Δ / update_fraction)
             = base_fee_old × (1 - 786432 / 5007716)
             ≈ base_fee_old × 0.8439 → 약 -15.6%

✔ EIP-7691 문서에서는 근사값으로 -14.5% 정도로 표현.
(정확도 차이는 rounding이나 blob가스 단위 단절에서 발생)

[EIP-7623] calldata 비용 차등화

덴쿤 업그레이드 이전, L2 롤업들은 데이터를 콜데이터로 이더리움에 저장했다. 이후 블롭이 도입되어 L2는 더 효율적인 방식으로 데이터를 저장하게 되었다.

그러나 블롭과 콜데이터 모두 이더리움 대역폭을 사용하기 때문에, 블롭이 많은 블록에서 콜데이터까지 많을 경우 P2P 네트워크에 과부하를 유발했다.

Calldata가 클 때 이더리움 체인 전체에 미치는 부정적 영향

  1. 블록 크기 증가 = 네트워크 부하 증가
  • 블록이 커질 수록:
    1. 전파 지연 증가 → 블록 도달 속도 느려짐
    2. 오퍼레이터들 간 분기 가능성 증가 → chain reorg 위험 증가
  1. 디스크 사용량 증가
  • calldata는 상태(state)가 아니라 체인 히스토리에 저장됨
  • 모든 노드가 전체 히스토리를 저장(풀노드 기준)
    calldata가 커지면:
    1. 체인 크기가 빠르게 증가
    2. 풀 노드 운영에 필요한 디스크 비용 상승
  1. 싱크 시간 증가
  • 신규 노드가 네트워크에 진입할 때, 과거 모든 블록을 다운로드
  • 블록마다 calldata가 크면:
    1. 동기화 시간도 길어지고
    2. 동기화 트래픽도 많아짐
  1. 실행 비용은 없지만, 리소스는 가득
  • calldata는 EVM에서 가스 비용만 들고 실행은 안됨 (ex. 그냥 데이터)
  • 하지만:
    1. 블록 크기 계산시 포함
    2. 블록 전파, 저장, 복원 등 리소스는 다 사용
  1. DoS 공격 경로
  • 공격자가 0x00으로 가득찬 calldata를 반복적으로 보내서
    1. 블록을 가득 채움
    2. 저렴한 비용으로 네트워크 자원 소비

변경점

  • 콜데이터 가격을 인상하되, 데이터 무거운 트랜잭션에만 선택적으로 적용
    - 기존 calldata 가스 비용: 0바이트당 4 가스, 1바이트당 16 가스
    - EIP-7623 적용 후: DA 중심 트랜잭션의 경우 0바이트당 12 가스, 1바이트당 48 가스로 증가
  • 최악의 경우 블록 크기를 제한
  • L2가 콜데이터 대신 블롭 사용을 유도
  • 전체 트랜잭션 중 99% 이상은 영향이 없음

즉, 네트워크 안정성을 위해 데이터 많은 콜데이터 트랜잭션만 비용을 높여 L2가 블롭을 사용하도록 유도하는 개선안이다.

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
	//1.기본 가스 설정
	var gas uint64
	if isContractCreation && isHomestead {
		gas = params.TxGasContractCreation
	} else {
		gas = params.TxGas
	}
	//2. 가스 비용 계산
	dataLen := uint64(len(data))
	// Bump the required gas by the amount of transactional data
	if dataLen > 0 {
		// Zero and non-zero bytes are priced differently
		//2-1. zero / non-zero 바이트 수 계산
		z := uint64(bytes.Count(data, []byte{0}))
		nz := dataLen - z

		// Make sure we don't exceed uint64 for all data combinations
		// 2-2. EIP-2028 조건에 따른 가스 비용 적용
		nonZeroGas := params.TxDataNonZeroGasFrontier // 일반적으로 68
		if isEIP2028 {
			nonZeroGas = params.TxDataNonZeroGasEIP2028 // EIP-2028 적용시 16
		}
		if (math.MaxUint64-gas)/nonZeroGas < nz {
			return 0, ErrGasUintOverflow
		}
		// 2-3. overflow 체크 + 가스 누적
		gas += nz * nonZeroGas

		if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
			return 0, ErrGasUintOverflow
		}
		gas += z * params.TxDataZeroGas

        // 3. EIP-3860: Init code 길이에 따른 가스 추가
		if isContractCreation && isEIP3860 {
			lenWords := toWordSize(dataLen)
			if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
				return 0, ErrGasUintOverflow
			}
			gas += lenWords * params.InitCodeWordGas
		}
	}
	// 4. EIP-2930: Access List 관련 가스
	if accessList != nil {
		gas += uint64(len(accessList)) * params.TxAccessListAddressGas
		gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
	}
	// 5. EIP-7702: SetCodeAuthorization 관련 가스
	if authList != nil {
		gas += uint64(len(authList)) * params.CallNewAccountGas
	}
	
	//6. 최종 반환
	return gas, nil
}
필드설명
data트랜잭션의 calldata (입력 데이터)
accessListEIP-2930의 액세스 리스트
authListEIP-7702 관련 authorization 목록
isContractCreation컨트랙트 생성 여부
isHomestead, isEIP2028, isEIP3860포크 여부에 따른 가스 정책 제어
  • calldata 가스 비용 계산 부분
// params/protocol_params.go
const (
    TxDataZeroGas            uint64 = 4
    TxDataNonZeroGasFrontier uint64 = 68
    TxDataNonZeroGasEIP2028  uint64 = 16
    TxDataZeroGasEIP7623     uint64 = 12
    TxDataNonZeroGasEIP7623  uint64 = 48
)
// core/state_transition.go

func IntrinsicGas(
...

dataLen := uint64(len(data))
    if dataLen > 0 {
        z := uint64(bytes.Count(data, []byte{0}))
        nz := dataLen - z

        zeroByteGas := params.TxDataZeroGas
        nonZeroGas := params.TxDataNonZeroGasFrontier
        if isEIP2028 {
            nonZeroGas = params.TxDataNonZeroGasEIP2028
        }
        // EIP-7623
        if isEIP7623 {
            zeroByteGas = params.TxDataZeroGasEIP7623
            nonZeroGas = params.TxDataNonZeroGasEIP7623
        }

        if (math.MaxUint64-gas)/nonZeroGas < nz {
            return 0, ErrGasUintOverflow
        }
        gas += nz * nonZeroGas

        if (math.MaxUint64-gas)/zeroByteGas < z {
            return 0, ErrGasUintOverflow
        }
        gas += z * zeroByteGas

        if isContractCreation && isEIP3860 {
            lenWords := toWordSize(dataLen)
            if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
                return 0, ErrGasUintOverflow
            }
            gas += lenWords * params.InitCodeWordGas
        }
    }
...
}

[참고] state_transition.go

효과

  • 블록 최대 크기 감소: 약 3.5 MiB에서 1.9 MiB로 감소
  • 블록 크기 변동성 감소: 일관된 블록 크기를 유지하여 네트워크 안정성 향상
  • 네트워크 효율성 향상: 데이터 중심 트랜잭션의 비용 증가로 불필요한 데이터 사용 억제

[EIP-7840] 블롭 스케줄을 EL 설정에 포함

이더리움은 블롭을 통해 L2 데이터를 더 효율적으로 저장한다.

이 블롭의 목표 수(target)와 최대 수(max)는 현재 네트워크 업그레이드에 따라 고정되거나, 클라이언트 간 Engine API를 통해 교환되어 설정된다.

var (
	// DefaultCancunBlobConfig is the default blob configuration for the Cancun fork.
	DefaultCancunBlobConfig = &BlobConfig{
		Target:         3,
		Max:            6,
		UpdateFraction: 3338477,
	}
	// DefaultPragueBlobConfig is the default blob configuration for the Prague fork.
	DefaultPragueBlobConfig = &BlobConfig{
		Target:         6,
		Max:            9,
		UpdateFraction: 5007716,
	}
	// DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork.
	DefaultOsakaBlobConfig = &BlobConfig{
		Target:         6,
		Max:            9,
		UpdateFraction: 5007716,
	}
	// DefaultBlobSchedule is the latest configured blob schedule for Ethereum mainnet.
	DefaultBlobSchedule = &BlobScheduleConfig{
		Cancun: DefaultCancunBlobConfig,
		Prague: DefaultPragueBlobConfig,
		Osaka:  DefaultOsakaBlobConfig,
	}
)

blob을 실제로 다루는 합의 계층에서 target blobs per blockmax blobs per block 등 정보를 가지고 있다. 하지만 현실적으로 이 값들을 실행 계층에서도 필요하다.

예를 들어, eth_feeHistory RPC 호출 결과에는 blobGasUsedRatio라는 필드가 있다. 이 필드는 최대 블롭 개수(max blobs)를 필요로 한다.

하지만 그때마다 실행 계층에서 매 블록마다 max blob값을 엔진 API로 받아오는것이 너무 과하다고 판단하여(maxBlobsPerBlock은 보통 프로토콜 수준에서 거의 고정되거나 드물게 변경되는 값) 실행 계층에 설정값으로 고정해 두는 것이다.

[참고] config.go

변경점

  • 실행 계층의 클라이언트 설정파일에 블롭 관련 스케줄 필드 추가
    - 예 : 블록당 목표 블롭 수, 최대 블롭 수, 블롭 수수료 조정 등

효과

  • 클라이언트가 직접 config 파일을 통해 블롭 관련 설정을 관리 → Engine API 복잡성 제거
    - 원래는 합의 계층의 클라이언트가 생성 계층으로 최신 블록 상태 및 다음 블록 생성 요청을 전달하는 API(engine_forkchoiceUpdated)에 blobGasTarget, maxBlobsPerBlock 같은 값도 함께 전달
  • 동적으로 블롭 수를 조정 가능 (업그레이드 없이도 config로 가능)
  • 네트워크 유연성과 확장성

[references]
https://ethereum.org/ko/roadmap/pectra/
https://www.coinex.land/ko/academy/detail/2575-the-ethereum-pectra-upgrade-your-ultimate-guide-to-the-future-of-eth?pId=2&sId=3

profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글