EIP-2718 타입드 트랜잭션 봉투 (Typed Transaction Envelope)

최종 확정 · EVM·실행 · 원문 상태 Final · 2020-06-13 작성

트랜잭션 맨 앞에 종류를 나타내는 번호 한 바이트를 붙이는 봉투(envelope) 형식을 도입해 앞으로 새 트랜잭션 종류를 서로 충돌 없이 추가할 수 있게 합니다.

왜 필요한가

지금까지 이더리움은 새 종류의 트랜잭션을 추가할 때마다 기존 트랜잭션과 겹치지 않게 만들어야 했습니다. 인코딩된 데이터만 보고 어떤 종류인지 구분할 수 있어야 하고 두 종류에 동시에 해당하는 트랜잭션이 생기면 안 됐기 때문입니다. 예를 들어 EIP-155는 새 값을 기존 필드 하나에 비트 단위로 끼워 넣는 방식으로 이 문제를 피했습니다. 그런데 외부 소유 계정이 자기 컨텍스트에서 코드를 직접 실행하게 하는 제안, msg.sender가 아닌 다른 사람이 가스비를 내게 하는 제안, 레이어 1 다중 서명 관련 제안처럼 새 트랜잭션 종류가 여럿 논의되면서 이들을 서로 호환되게 설계하는 일이 EIP 작성자와 클라이언트 모두에게 큰 부담이 됐습니다. 이 EIP는 트랜잭션 맨 앞에 종류 번호 한 바이트를 붙이는 봉투 구조를 도입해 기존 트랜잭션과의 호환성만 한 번 지키면 그 뒤로는 번호가 겹치지 않게만 관리하면 되도록 문제를 단순하게 만듭니다.

무엇이 바뀌나

지금

EIP-2718 이후

누구에게 영향이 있나

심층 설명

트랜잭션 구조: TransactionType || TransactionPayload

FORK_BLOCK_NUMBER부터 블록 헤더의 트랜잭션 루트는 patriciaTrie(rlp(Index) => Transaction)의 루트 해시여야 합니다(MUST). 여기서 Index는 블록 안에서 이 트랜잭션의 위치이고 Transaction은 TransactionType || TransactionPayload 또는 LegacyTransaction입니다(||는 바이트 연결 연산자). TransactionType은 0과 0x7f 사이의 양의 8비트 숫자이고 TransactionPayload는 해석이 타입에 달린 불투명한 바이트 배열로 구체적 형식은 이후 EIP가 정의합니다. LegacyTransaction은 rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])입니다.

영수증도 같은 봉투 구조를 씁니다

영수증 루트도 patriciaTrie(rlp(Index) => Receipt)의 루트 해시여야 하며(MUST), Receipt는 TransactionType || ReceiptPayload 또는 LegacyReceipt입니다. ReceiptPayload는 타입에 따라 해석되는 불투명한 바이트 배열이고 LegacyReceipt는 rlp([status, cumulativeGasUsed, logsBloom, logs])입니다. 영수증의 TransactionType은 같은 Index를 가진 트랜잭션의 TransactionType과 반드시 일치해야 합니다(MUST).

서명 데이터 첫 바이트에 타입을 넣는 권고

앞으로 나올 트랜잭션 타입의 서명은 TransactionType을 서명 대상 데이터의 첫 바이트로 포함해야 합니다(SHOULD). 한 타입의 서명이 다른 타입의 서명으로 재사용되는 일을 막기 위해서입니다. MUST가 아니라 SHOULD인 이유는 예외가 있기 때문입니다. 레거시 서명 방식과 호환돼야 하는 래핑된 레거시 트랜잭션에서는 불가능하고 전통적 의미의 서명 없이 다른 방식으로 유효성을 판단하는 트랜잭션도 있을 수 있습니다. Security Considerations는 이를 지키지 않으면 다른 타입의 트랜잭션과 서명이 호환돼 사용자에게 보안 취약점이 생길 수 있다며 강하게(STRONGLY) 권고합니다.

타입 번호를 0x7f까지로 제한한 이유

가까운 미래에는 0x7f까지로 충분하고 상위 비트를 연속(continuation) 비트로 쓰는 식으로 범위를 넓힐 선택지도 남아 있습니다. 또 레거시 트랜잭션은 항상 0xc0 이상의 바이트로 시작하므로 0~0x7f 범위를 쓰면 충돌하지 않습니다. 그래서 클라이언트는 첫 바이트가 [0, 0x7f] 범위면 새 타입, [0xc0, 0xfe] 범위면 레거시로 구분할 수 있습니다. 0xff는 RLP 인코딩 트랜잭션에서 현실적으로 나올 수 없는 값이라 미래의 확장 신호(extension sentinel) 값으로 예약돼 있습니다.

페이로드를 불투명 바이트로 둔 이유

타입 바이트 뒤를 RLP 리스트가 아니라 불투명한 바이트 배열로 정의해서 앞으로 SSZ, LEB128, 고정 폭 형식 같은 다른 인코딩도 쓸 수 있게 열어 뒀습니다. 타입 번호를 어떻게 배정할지 정하는 알고리즘도 논의됐지만 표준 작성 시점에는 강한 필요가 없어 범위 밖으로 뒀고 필요해지면 미래 EIP가 배정 표준을 만들 수 있다고 적었습니다.

ORIGIN과 CALLER는 타입과 무관하게 유지

트랜잭션 타입마다 ORIGIN과 CALLER 옵코드가 돌려주는 값을 다르게 정의하자는 논의가 있었지만 채택하지 않았습니다. 컨트랙트가 트랜잭션 종류를 구분해 다르게 동작하는 일을 막으려고 타입을 컨트랙트에게 불투명하게 두고 싶었고 ORIGIN과 CALLER에 대한 가정 위에 만들어진 기존 컨트랙트와의 호환성 우려도 있었습니다. 앞으로 모든 트랜잭션 타입은 첫 EVM 프레임의 CALLER를 합리적으로 나타내는 주소를 가지며 ORIGIN은 모든 경우 그와 같은 주소라고 가정합니다. 타입이 컨트랙트에 추가 정보를 줘야 한다면 새 옵코드가 필요합니다. 이 EIP는 상태가 Final로 확정된 표준이며 EIP-1559나 EIP-2930 같은 이후 타입드 트랜잭션들의 공통 기반이 됐습니다.

EIP 원문 · ← EIPs