이더리움의 누출된 가스 탱크: 13가지 값비싼 가스 모델 불일치 공개

이 기사는 기계로 번역되었습니다
원문 표시

이더리움의 13가지 가스 모델 불일치

이 게시물은 이더리움 가스 모델의 13가지 불일치점을 요약합니다.
모든 심볼은 이더리움 옐로페이퍼(특히 부록 G. 수수료 일정)에서 나왔습니다.


1. 외부 거래는 계정을 생성할 때 신규 계정 수수료( G_{newaccount} G n e w a c c o u n t )가 발생하지 않지만 내부 거래는


아직 존재하지 않는 새로운 계정 A 로 이더리움(ETH) 이체하는 계약 C 필요하다고 가정해 보겠습니다.

  • C 직접 이체하는 경우 내부 신규 계정 경로는 25,000가스 ( G_{newaccount} G n e w a c c o u n t )를 청구합니다.
  • 대안: 먼저 EOA에서 A 로 1 Wei. 이 tx는 기본 가스 21,000 개비입니다. 그 후 A 존재합니다. 이제 CA 에게 이체할 때 더 이상 신규 계정 수수료가 발생하지 않습니다( 약 4,000개비 절약 ).

결론
계약에 따라 새 계정을 만드는 경우 25,000의 가스비를 지불합니다.
하지만 먼저 외부 거래(21,000가스)를 보낸 다음 계약을 통해 자금을 보내면 실제로 는 약 4,000가스 정도를 절약할 수 있습니다 .

원인
외부 및 내부 생성 경로는 서로 다른 청구 방식을 사용합니다. 명시적인 신규 계정 수수료를 트리거하는 것은 해당 계약을 호출하는 경우에만 가능합니다.

2. 사전 컴파일 호출은 때때로 트랜잭션 입력 바이트 수수료를 건너뜁니다.


미리 컴파일된 컨트랙트(예: ECRECOVER )를 호출하면 트랜잭션 입력 바이트에 대한 요금 청구를 건너뛸 수 있습니다. 일반적으로 입력 바이트는 각각 4가스(0) 또는 16가스(0이 아님)의 비용이 발생합니다. 두 가지 실제 트랜잭션 사례를 살펴보겠습니다.
* 1. 24,276 가스를 사용하여 ECRECOVER 호출하는 트랜잭션 0x6b01 , 여기서 276 가스는 입력 바이트에 대한 것입니다.
* 2. 24,000 가스로 ECRECOVER 호출하는 트랜잭션 0x1fb0 , 여기서 0 가스는 입력 바이트에 대한 것입니다.
실행 클라이언트의 소스 코드를 살펴보면 두 번째 트랜잭션도 입력 바이트에 대한 요금을 부과하지 않고 ECRECOVER 실행할 수 있음을 알 수 있습니다. 클라이언트는 예상 크기에 맞춰 입력 데이터를 0으로 채웁니다.

추가 참고 사항
당신은 제가 언급한 두 가지 거래가 외부 거래라는 것을 알아차릴 만큼 똑똑하군요.
그렇다면 왜 미리 작성된 계약서를 적용하는 걸까?

그 이유 중 하나는 일부 탐색기가 사전 컴파일 주소(0x01–0x0A)를 "번 주소"로 잘못 표시하여 사용자를 더욱 혼란스럽게 하기 때문이라고 생각합니다(아래 스냅샷참조 ).

화상 주소
화상 주소 539×182 39.1KB

게다가 이러한 특수 주소(0x01–0x0A)에 사전 컴파일 주소를 배포하는 것은 실패한 설계입니다.
가끔 사람들은 이 특별한 주소로 직접 전화를 걸고 싶어합니다.

원인
사전 컴파일된 계약의 열악한 주소 설계와 블록 탐색기의 오해로 인해 혼란과 잘못된 라벨링이 발생합니다.

3. 액세스 목록 항목은 액세스되지 않은 경우에도 요금이 청구됩니다(예: G_{accesslistaddress} G a c c e s s l i s t a d d re s s , G_{accessliststorage} G a c c e s s l i s t s t o r a g e )


이더리움 개선 제안(EIP)-2930은 액세스 리스트를 도입하여 트랜잭션이 접근할 주소와 저장 슬롯을 지정할 수 있도록 합니다. 그러나 트랜잭션은 액세스 리스트에 주소와 슬롯을 포함할 수는 있지만, 실제로는 이를 건드리지 않을 수 있습니다.
예를 들어, 트랜잭션 0x0dd0c 는 액세스 목록을 설정하지만 주소로 인해 지정된 슬롯에 액세스하지 못합니다.

원인
이 프로토콜은 항목 사용 여부와 관계없이 실행을 간소화하기 위해 포함 에 대한 요금을 부과합니다. 사용자가 올바른 입력을 제공할 수 있다고 믿는다면, 테일러 스위프트를 당신의 아내로 믿는 것이나 마찬가지입니다.

4. 셀프 전송은 여전히 ​​전송 가스를 충전합니다.


계정 A가 자기 자신에게 이더리움(ETH) 보냅니다.
잔액 변동은 없지만, 가치 이전을 위해 9,000가스 ( G_{callvalue} G call v a l l v a l u e )가 여전히 청구됩니다. @vbuterin게시물 에 따르면,

두 개의 계정 쓰기(잔액 편집 CALL은 일반적으로 9000가스 비용이 듭니다)

왜 계정 하나 만드는 데 9,000 가스 가 드는 걸까요? 실제로 실행 클라이언트 소스를 읽어보면, 보낸 사람 주소와 받는 사람 주소가 같으면 클라이언트가 아무 작업도 하지 않는다는 것을 알 수 있습니다.

위의 경우는 거래가 자체 이체이거나 CALLCODE를 사용하여 가치를 이체하는 경우 발생할 수 있습니다.

원인
전송이 무작업인지 여부에 관계없이 실행 요금이 발생합니다.

5. Calldata와 Contract Bytecode 디스크 가격 불일치

  • Tx calldata: 16가스/바이트(0이 아닌 경우) 또는 4가스/바이트(0) .
  • 계약 바이트코드: 200 가스/바이트 .
    둘 다 디스크를 사용하지만 가격이 일정하지 않습니다. tx calldata가 contract bytecode보다 실제 디스크 사용량과 네트워크 오버헤드를 고려해야 하기 때문에 더 저렴하다는 점이 매우 혼란스럽습니다.

원인
가스 일정은 실제 디스크 사용량에 맞춰 조정하지 않고 "호출 데이터"와 "코드 입금"을 분리합니다.

6. 되돌려진 거래는 디스크에 쓴 것처럼 청구됩니다.


되돌린 트랜잭션은 메모리의 상태를 수정하지만 변경 사항은 유지되지 않습니다. 그러나 쓰기에 대한 요금은 계속 적용되며, 다음과 같은 가스 요금이 영향을 받습니다.

  • 25,000 가스 ( 신규 계정 , G_ { newaccount } G 계정 )
  • 9,000 가스 ( 가치 전송 , G_ { callvalue } G call value )
  • 2,100 가스 (콜드 슬롯 , G_ { coldslot } G 콜드 슬롯 )
  • 200 가스 ( 코드 입금 , G_ { codedeposit } G 코드 입금 )

실제 메모리 전용 비용은 ~ 100가스 였을 것입니다.

원인
실행 중에 가스가 청구되며, 나중에 되돌리면 상태 변경은 취소되지만 수수료는 취소되지 않습니다. 구현 시 서비스 거부(DoS)를 방지하기 위해 보수적으로 요금을 청구합니다.

7. 단일 거래에서 여러 이더리움(ETH) 전송이 콜드체인으로 잘못 청구되었습니다.


단일 거래 내에서 계약이 이더리움(ETH) 여러 계정으로 보낸다고 가정해 보겠습니다.

  • 첫 번째 전송은 G_{callvalue} G 호출 ( 9,000 가스 ) 올바르게 발생 시켜 계정 잔액에 기록합니다.
  • 동일 거래에서 다른 계좌로 후속 이체를 하는 경우 웜 액세스 수수료( 100가스 + 4,500가스 )가 부과되지만, 때로는 여전히 콜드 액세스 수수료( 9,000가스 )로 청구됩니다.

원인
웜/콜드 액세스 회계는 단일 거래 내에서 여러 가치 이전에 대해 지속적으로 업데이트되지 않습니다.

8. 마이너/검증자 보상 또는 출금 쓰기는 무료입니다.


프로토콜 수준의 잔액 업데이트(예: 보상, 인출)는 디스크의 상태를 수정하지만 가스 비용은 0입니다 .

원인
시스템 수준의 회계는 가스 회계 절차를 우회합니다.

9. SSTORE의 첫 번째 디스크 읽기는 무료입니다( 이더리움 개선 제안(EIP)-2200에 따라)


SSTORE 명령어가 실행되면 먼저 디스크(컨트랙트 저장소)에서 현재 값을 읽은 후 새 값을 쓸지 여부를 결정합니다. 이더리움 개선 제안(EIP)-2200 에 따르면, 저장된 값이 기존 값과 일치하면 디스크 쓰기가 발생하지 않고 최소한의 가스 요금만 부과됩니다. 그러나 초기 디스크 읽기 자체에는 가스 요금이 부과되지 않습니다 . 프로토콜은 값이 변경되는 경우에만 후속 쓰기에 대한 가스 요금을 부과합니다.

원인
이더리움 개선 제안(EIP)-2200의 로직은 상태 변경에 대한 요금 부과에 초점을 맞추지만, 항상 먼저 발생하는 디스크 읽기에 대한 요금 부과는 생략합니다. 즉, 콜드 읽기(cold read)일지라도 스토리지 슬롯에 대한 첫 번째 접근은 무료입니다.

10. 스토리지 읽기 최적화로 I/O가 감소했지만 가스는 변경되지 않았습니다.


이더리움 클라이언트는 플랫 스토리지/ 스냅샷 최적화(예: Geth의 스냅샷 가속 구조 )를 채택했습니다. 이 최적화는 상태를 플랫 키-값 저장소로 구성하고 기존 Merkle-Patricia Trie(MPT)에 필요한 중간 노드를 우회하여 직접 디스크 읽기를 허용합니다. 이 최적화는 콜드 스토리지 읽기 시 디스크 I/O를 크게 줄입니다. 예를 들어, Geth와 다른 클라이언트는 이제 SAS 또는 유사한 구조를 사용하지만, 콜드 액세스에 대한 가스 요금 (2,600 / 2,100 / 2,400 / 1,900 가스) 은 변동이 없습니다.

원인
콜드 액세스용 가스 상수는 원래 MPT(Multiple Time Transfer)를 위해 보정되었는데, MPT에서는 여러 트라이 노드를 통과해야 하기 때문에 디스크 읽기 비용이 더 많이 들었습니다. SAS를 사용하면 실제 디스크 리소스 소비량이 훨씬 낮아지지만, 프로토콜은 해당 가스 요금을 업데이트하지 않았습니다.

완화
클라이언트가 SAS 또는 유사한 최적화된 스토리지 백엔드로 전환하면 감소된 디스크 I/O를 반영하여 가스 상수를 재보정합니다.

11. SLOAD 대 MLOAD 가격 불일치

  • SLOAD (따뜻함) → 100가스
  • MLOAD3가스
    둘 다 메모리 읽기 방식이지만, 가격은 크게 다릅니다.

원인
상태 작업과 메모리 작업 간의 기존 구분; 최적화로 인해 실제 비용 격차가 모호해졌습니다.

12. 내부 거래는 때때로 가스 없이 계정을 업데이트합니다.


디스크에서 계정 업데이트가 가스 요금 없이 발생하는 경우입니다. 특히, 이 문제는 사용자가 외부 트랜잭션을 계약 A로 전송하고, 계약 A가 계약 B를 내부적으로 호출하는 상황에서 발생합니다. 계약 B가 스토리지의 슬롯을 수정하면 계약 B 계정의 해당 스토리지 루트가 디스크에서 업데이트되어야 합니다. 그러나 이 계정 B 업데이트에는 가스 요금이 부과되지 않아 불일치가 발생합니다.

원인
이 버그는 계약 B의 스토리지 트리 수정 시 계정 상태 업데이트에 추가 가스 요금이 발생하지 않기 때문에 발생합니다. 이는 디스크 쓰기가 수행됨에도 불구하고 프로토콜이 내부 트랜잭션으로 인해 발생하는 계정 상태 업데이트에 요금을 부과하지 않기 때문입니다.

13. EXT* 명령어 가격이 너무 비싸다


EXTCODESIZE BALANCE 보다 더 많은 데이터를 읽을 수 있지만, 둘 다 동일한 콜드 계정 수수료( 2,600가스 )가 부과됩니다.

원인
명령어 가격 책정 버킷은 거칠고 가변적인 작업을 무시합니다.

마무리 노트

이 문제는 저의 논문에서 다음과 같이 나왔으며, 이 링크(Chainlink) 로 공유합니다.

He, Z., Li, Z., Luo, J., Luo, F., Duan, J., Li, J., … & Zhang, X. (2025년 2월). Auspex: 블록체인 거래 수수료 메커니즘의 불일치 버그 발견. 제23회 USENIX 파일 및 저장 기술 컨퍼런스 논문집.

제 논문을 인용해 주시면 감사하겠습니다.

이 모든 것은 이더리움 프로토콜 내 가스 가격 책정 메커니즘에 대한 포괄적인 검토 및 조정의 필요성을 강조합니다. 이러한 불일치를 해결함으로써 다양한 작업의 기본 자원 비용을 정확하게 반영하는 더욱 효율적이고 공정한 가스 시장을 확보할 수 있습니다.


출처
면책조항: 상기 내용은 작자의 개인적인 의견입니다. 따라서 이는 Followin의 입장과 무관하며 Followin과 관련된 어떠한 투자 제안도 구성하지 않습니다.
라이크
즐겨찾기에 추가
코멘트