EIP-2930: Optional access lists

Static Badge Static Badge

트랜잭션이 접근할 수 있는 계정 주소와 스토리지 슬롯을 명시적으로 지정할 수 있도록 하는 접근 목록(access list)을 도입하는 EIP입니다.

EIP-2718과 함께 적용되어 새로운 트랜잭션 유형인 0x01을 사용합니다.

Berlin 포크와 함께 적용되었습니다.

적용일시: Apr 15, 2021, 10:07:03 AM +UTC


초록

0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS]) 형식의 새로운 EIP-2718 트랜잭션 유형을 도입합니다.

accessList는 계정 주소와 스토리지 키의 목록을 지정합니다. 이 주소와 스토리지 키는 EIP-2929에서 도입된 accessed_addressesaccessed_storage_keys 글로벌 집합(global set)에 추가됩니다. 접근 목록 밖의 항목에 접근하는 것과 비교하여 상대적으로 할인된 가스 비용으로 접근 목록 안의 항목에 접근할 수 있습니다.


등장 배경

이 EIP는 두 가지 기능을 제공합니다.

  1. EIP-2929에서 소개된 컨트랙트 손상 위험을 완화합니다. 접근 목록 트랜잭션은 사용하고자 하는 계정 주소와 스토리지 슬롯을 사전에 지정하고 비용을 지불할 수 있습니다. 그 결과, 실제 트랜잭션 실행에서는 SLOADEXT* opcode들의 비용이 100 gas 밖에 들지 않습니다. 이는 컨트랙트 손상 위험을 낮출 뿐만 아니라, EIP-1884로 인해 멈춘 컨트랙트를 다시 동작하게 만들 수 있을 만큼 낮은 비용입니다.

  2. 접근 목록 형식 및 해당 형식을 처리하는 로직을 소개합니다. 이는 향후에 다양한 용도로 용도 변경이 될 수 있습니다. (including block-wide witnesses, use in ReGenesis, moving toward static state access over time)


세부 사항

정의

  • TransactionType: 0x01 (EIP-2718)
  • ChainId: 트랜잭션은 해당 체인의 ID와 일치하는 경우에만 유효합니다.
  • YParity: 서명 데이터의 Y 좌표의 패리티입니다. 0x00 또는 0x01입니다.

파라미터

ConstantValue
FORK_BLOCK12244000
ACCESS_LIST_STORAGE_KEY_COST1900
ACCESS_LIST_ADDRESS_COST2400

설명

  • FORK_BLOCK을 기준으로, 새로운 EIP-2718 트랜잭션 유형이 적용됩니다. 이 트랜잭션의 TransactionType0x01입니다.
  • 이 유형의 EIP-2718 TransactionPayloadrlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS]) 형식입니다.
  • signatureYParity, signatureR, signatureSkeccak256(0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]))에 secp256k1 서명을 적용한 결과를 나타냅니다.
  • 이 유형의 EIP-2718 ReceiptPayloadrlp([status, cumulativeGasUsed, logsBloom, logs]) 형식입니다.

이 유형의 트랜잭션이 유효하려면 accessList의 타입은 [[{20 bytes}, [{32 bytes}...]]...]이어야 합니다. 이 때 ...은 0개 이상의 요소를 의미합니다. 예를 들어, 다음과 같은 형식이 유효합니다.

[
    [
        "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", # address
        [
            "0x0000000000000000000000000000000000000000000000000000000000000003", # storage key
            "0x0000000000000000000000000000000000000000000000000000000000000007"
        ]
    ],
    [
        "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
        []
    ]
]

트랜잭션이 실행되는 시점(EIP-2028에 따라 21000 + 4 * zeroes + 16 * nonzeroes 시작 가스가 청구되는 시점과 동일)에, 접근 목록에 대한 추가 비용이 청구됩니다. 이 비용은 주소당 ACCESS_LIST_ADDRESS_COST와 스토리지 키당 ACCESS_LIST_STORAGE_KEY_COST입니다. 이 비용은 ACCESS_LIST로 지정된 주소와 스토리지 키의 수에 따라 달라집니다. 예를 들어, 위의 예시에서는 ACCESS_LIST_ADDRESS_COST * 2 + ACCESS_LIST_STORAGE_KEY_COST * 2가 청구됩니다.

중복된 계정 주소와 스토리지 키가 접근 목록에 포함될 수 있지만, 추가 비용이 발생합니다. 그러나 중복된 값이 적용되지 않은 경우와 비교해 트랜잭션 실행 결과에는 아무런 영향을 미치지 않습니다. (가스비만 더 나간다는 의미)

계정 주소와 슽리지 키는 즉시 accessed_addresses 그리고 accessed_storage_keys 글로벌 집합으로 로드됩니다. 이는 다음의 로직으로 구현됩니다.

def process_access_list(access_list) -> Tuple[List[Set[Address], Set[Pair[Address, Bytes32]]], int]:
    accessed_addresses = set()
    accessed_storage_keys = set()
    gas_cost = 0
    assert isinstance(access_list, list)
    for item in access_list:
        assert isinstance(item, list) and len(item) == 2
        # Validate and add the address
        address = item[0]
        assert isinstance(address, bytes) and len(address) == 20
        accessed_addresses.add(address)
        gas_cost += ACCESS_LIST_ADDRESS_COST
        # Validate and add the storage keys
        assert isinstance(item[1], list)
        for key in item[1]:
            assert isinstance(key, bytes) and len(key) == 32
            accessed_storage_keys.add((address, key))
            gas_cost += ACCESS_LIST_STORAGE_KEY_COST
    return (
        accessed_addresses,
        accessed_storage_keys,
        gas_cost
    )

접근 목록은 트랜잭션 데이터처럼 바이트당 요금으로 청구되지 않습니다. 대신, 항목당 비용으로 청구되는데, 이는 트랜잭션을 평가할 때 각 항목에 접근하는 비용에 더해 접근 목록 데이터의 대역폭 비용을 포함합니다.


논리적 근거

접근 목록 안의 항목에 접근하는 것은 더 저렴하다

이는 가능한 접근 목록을 사용하는 것을 권장하기 위함입니다. 왜냐하면 스토리지 읽기 작업이 예측이 가능해지면 트랜잭션을 처리하는 것이 더 쉬워지기 때문입니다. (클라이언트가 트랜잭션을 받으면 데이터베이스에서 미리 로드, 트랜잭션을 수신했을 때 증인을 요청, 또는 최소한 데이터를 병렬로 로드할 수 있습니다.)

복사를 허용한다

최대한 간단하게 구현하기 위해 중복 항목을 허용합니다 (그렇지 않으면 왜 중복을 허용하지 않는지에 대해 구구절절 설명해야 합니다.) 항목이 중복되더라도 집합에 추가하면 중복된 값은 적용되지 않기 때문에 실제로는 체인에 저장되는 데이터는 부풀려지지 않습니다. (가스비만 더 나간다는 의미)

서명은 트랜잭션 유형과 데이터에 적용된다

트랜잭션이 다른 유형으로 재해석되는 것을 방지하기 위해 트랜잭션 유형을 서명에 포함시킵니다.


하위 호환성(Backwards Compatibility)

이 EIP은 "예상치 못한" SLOAD 및 계정 액세스를 수행하는 데 더 많은 가스를 필요로 합니다. 가스는 선결제되어 있으므로 고정 가스 로컬 호출(fixed-gas local calls)에는 영향을 미치지 않습니다. 따라서 이전에 가스 비용 증가가 컨트랙트를 멈추게 만들었던 경우만큼 심각한 영향을 미치지는 않습니다. 그러나 이는 스토리지 접근에 크게 의존하는 어플리케이션들의 경제적인 측면에 악영향을 미칠 수 있습니다.


보안 고려사항(Security Considerations)

접근 목록 생성

액세스 목록은 많은 상황에서 실시간으로 구성하기 어려우며, 이는 트랜잭션 생성과 서명 사이에 높은 시간 지연이 있거나 트랜잭션 생성기의 단순성이 높게 평가되는 환경에서 더욱 악화됩니다(예: 하드웨어 지갑에서 둘 중 하나 또는 둘 다 적용될 수 있음).

  • 그러나 이 EIP는 접근 목록에 대해 오직 10%의 초기 할인만을 제공하므로, 접근 목록을 구태여 생성할 필요는 없습니다. 참고로 앞으로 도구들이 개발되고 액세스 목록 생성이 더 성숙해지면 액세스 목록 외부의 상태에 액세스하는 비용이 시간이 지남에 따라 증가할 것으로 예상됩니다.

트랜잭션 크기 팽창

액세스 목록 사용으로 인해 평균 블록 크기가 증가할 것으로 예상됩니다. 접근 목록의 바이트당 비용은 스토리지 키가 1900 / 32 = 59.375, 주소가 2400 / 20 = 120으로 calldata보다 훨씬 비싸기 때문에 최악의 경우(?) 블록 크기는 증가하지 않을 것입니다. 또한 평균 블록 크기의 증가로 인한 비용은 접근 목록을 활용하여 스토리지를 읽는 것이 더 쉬워지고, 따라서 블록을 처리하는 데 더 적은 리소스가 필요하게 됨으로써 상쇄될 것입니다.


참고 자료