이더리움 설계 근거

원문: https://github.com/ethereum/wiki/wiki/Design-Rationale

이더리움의 디자인 원칙 문서를 요약해 봅니다. 위키에 기술된 내용인데, 상당히 깊은 부분의 내용들도 포함되어 있습니다. 내용을 모두 번역하기는 양이 많아서, 적당히 중요한 부분들만 정리하였습니다.

Principles

  1. Sandwitch complexity model: 이더리움의 하부 레벨 아키텍처는 가능한 단순하게 만들고, 인터페이스를 가능한한 쉽게 만든다. 복잡도가 발생할 수 없는 부분은 “미들 레이어”에 배치하며 이 부분은 코어 컨센서스 영역은 아니지만 엔드유저에게 보이지도 않는다. 여기 해당하는 것은 고수준 컴파일러, 인자 직렬화, 스토리지의 데이터 스트럭처 모델, leveldb 스토리지 인터페이스와 wire protocol 등.
  2. Freedom: 사용자는 이더리움 프로토콜을 어떤 용도로 사용하든 제약을 받지 않는다. 사용의 목적에 따라 특정한 이더리움 컨트랙트나 트랜잭션을 차별하려 하지 않는다.
  3. Generalization: 프로토콜 피처나 opcode는 최대한 로우레벨 컨셉들을 구현함. 이를 통해 다양한 형태로 재구성/활용될 수 있도록 하기 위해.
  4. We have No Features: 매우 흔히 사용되는 하이레벨 유스 케이스라도 프로토콜의 본질적인 기능으로 추가하지는 않는다. 꼭 필요하다면 컨트랙트 내부의 서브 프로토콜로 만들면 된다.

Blockchain-level protocol

Account based

비트코인은 UTXO를, 이더리움은 Account를 사용한다.

UTXO는 다음의 장점을 가진다.

  1. 높은 수준의 프라이버시: 각각의 트랜잭션마다 다른 주소를 사용하게 되면, 추적하기 매우 어렵다. 통화인 경우 이는 잘 적용되나 Dapp의 경우 이는 문제를 가진다. 사용자의 상태 정보를 추적해야 하는 경우가 많기 때문
  2. 확장성 패러다임으로의 가능성: UTXO는 이론적으로 특정한 확장성 패러다임에 잘 맞는다. 오직 특정 코인의 소유자만이 소유권에 대한 머클 트리를 관리하기 때문에, 소유자를 포함한 모든이가 그 데이터를 잊기로 한다면, 소유자에게만 피해가 간다.

Accounts는 다음의 장점을 가진다

  1. 공간 절약: UTXO 모델에서 300바이트를 사용할 때 Account 모델은 30바이트만 사용한다. 현실적으로는 이렇게 큰 차이가 나진 않는데, account 역시 파트리샤 트리에 저장되기 때문이다. 또한 트랜잭션 역시 더 작은데 이는 각각의 트랜잭션이 오직 하나의 리퍼런스와 시그너처를 필요로 하기 때문이다.
  2. 더 나은 대체성: 특정 코인의 소스에 대한 블록체인 레벨의 개념이 없기 때문에 코인의 출처에 따라 레드리스트/블랙리스트를 적용하기 어렵다
  3. 단순성: 스크립트를 작성하고 이해하기 쉽다
  4. 경량 클라이언트 참조: 경량 클라이언트는 상태 트리를 읽음으로써 어느 시점에서든 계정에 관계된 모든 정보를 읽을 수 있다.

어카운트 모델의 약점은 리플레이 어택을 막기 위해 모든 트랜잭션이 반드시 논스(nonce)를 가져야 하고, 마지막으로 사용된 논스보다 1큰 값이 아니면 트랜잭션을 받아 들이지 않는다는 점이다. 이 것은 더 이상 사용되지 않는 계정도 어카운트 스테이트에서 제거될 수 없다는 것을 의미한다.

Merkle Patricia Tree 링크

이더리움의 기본 데이터 구조. 모든 어카운트의 상태와 트랜잭션, 영수증들을 각각의 블록에 저장한다. 머클 트리와 패트리샤 트리를 결합한 것으로 각각의 엘리먼트들을 이용하여 아래와 같은 속성들을 가지는 구조를 생성한다.

  1. 루트 해시에 유일하게 매핑되는 고유의 키-밸류 쌍.
  2. 키-밸류 쌍의 추가, 수정, 삭제는 대수적인 (logarithmic) 시간 내에 처리됨. 즉 최대 소요 시간이 일정한 한도 내에서 유지된다

이는 모든 상태 트리에 대해 효율적이고 쉽게 갱신할 수 있는 “fingerprint”를 제공할 수 있게 한다. 이더리움 MPT는 여기에 기술되어 있다.

MPT는 아래와 같은 설계 결정사항을 포함한다.

  1. 두 가지 클래스의 노드들. kv 노드와 diverge 노드. kv 노드는 64단계의 트리를 탐색할 필요 없이 해당 노드로 가는 “shortcut”으로 동작한다. 이는 효율성을 높인다
  2. Diverge노드는 hexary이고 binary가 아님: 이 것은 lookup 효율성을 높이기 위해 결정되었음. 하지만 최적의 상태는 아닌 것이 확인되어 1.1에서는 재구성할 예정.
  3. 빈 값과 non-membership간의 차이 없음: 단순성을 위한 결정. 예를 들어 잔고에 값이 설정되지 않았을 경우 0으로 간주. 이더리움의 기본값에서 잘 동작하지만 일반성을 희생시키는 것은 사실.
  4. 종단 노드와 비종단 노드의 구분: 기술적으로 종단 노드임을 표시하는 플래그는 불필요함. 이더리움의 모든 트리는 고정된 키 길이를 가지기 때문. 하지만 일반성을 높이기 위해 추가되었고, 이더리움의 MPT 구현이 그대로 다른 암호화 프로토콜에도 적용되기를 기대함.
  5. “안전한 트리”의 키로서 sha3(k)를 사용: 상태와 어카운트 저장소 트리에 사용됨.

RLP (Recursive length prefix)

이더리움의 메인 직렬화 포맷. 블록, 트랜잭션, 어카운트 상태, 와이어 프로토콜 메시지등 모든 부분에 사용됨. 매우 최소화된 직렬화 포맷으로 유일한 목적은 중첩된(nested) 바이트 어레이를 저장하는 것임. protobuf, BSON등과는 다리 RLP는 어떠한 데이터 타입도 정의하지 않음.

구조를 중첩된 배열의 형태로 저장하고, 각 배열의 의미를 결정하는 것은 프로토콜에게 맡김. 키/밸류 맵 역시 명시적으로 지원되지 않음. RLP를 사용하는 이유는

  1. 구현의 단순성
  2. 바이트 단위의 확실한 일관성 확보

많은 언어에서 키/밸류 맵은 명시적인 순서를 가지지 않고, 부동 소수점 포맷은 많은 특별한 경우를 가지고 있음. 이로 인해 동일한 데이터가 다른 인코딩으로 만들어지고 결과적으로 다른 해시를 가지게 될 가능성이 있음. 프로토콜을 직접 만듦으로써 이러한 목표들에 맞게 설계되었는지를 확실히 할 수 있음

Compression Algorithm

와이어 프로토콜과 데이터베이스 모두 데이터를 저장할 대 커스텀 압축 알고리듬을 사용함. 이 알고리듬은 run-length-encoding zero로. 0이 아닌 다른 값들은 그대로 내버려 둠. (sha3(”)와 같은 일부 예외 제외)

Trie Usage

이더리움 블록체인의 각 블럭 헤더는 세 트리에 대한 포인터를 가진다.

  1. 상태트리 : 블록 액세스 후의 전체 상태 표시
  2. 트랜잭션 트리: 인덱스를 키로 사용하는 블록의 모든 트랜잭션 표시 (key 0: 첫번째 트랜잭션, key1: 그 다음 트랜잭션..)
  3. 영수증 트리: 각 트랜잭션에 해당하는 영수증 표시. 트랜잭션의 영수증(receipt)은 RLP 인코딩 된 자료구조이다.

[ medstate, gas_used, logbloom, logs]

  • medstate: 트랜잭션을 처리한 후 상태 트리의 루트
  • gas_used: 트랜잭션 처리가 끝난 후 사용된 개스의 양
  • logs: [address, [topic1, topic2 ..], data] 형태를 가지는 아이템들의 리스트. 트랜잭션의 실행중 LOG0 .. LOG4 opcode로 생성되는 로그. address는 컨트랙트의 주소. topic은 최대 4개까지의 32바이트 값이며, 데이터는 임의 길이의 바이트 배열임
  • logbloom: 트랜잭션에 포함된 모든 로그의 주소와 토픽으로 구성된 블룸 필터

블록 헤더에는 또한 블룸이 있는데, 이는 블록에 포함된 트랜잭션들의 모든 블룸을 OR 연산한 것이다. 이 구조의 목적은 이더리움 프로토콜을 경량 클라이언트에 대해 가능한 많은 부분에서 친화적으로 만들기 위함이다.

Uncle Incentivization

“Gredy Heaviest Observed Subtree” (GHOST) 프로토콜은 Yonatan Sompolinsky와 Aviv Zohar가 2013년 겨울에 발표한 것으로 더 빠른 블록 시간을 방해하는 문제들을 풀기 위한 시도이다. 현재 블록체인의 빠른 승인 시간은 높은 stale rate로 인해 보안성이 낮아진다는 문제가 있다. 왜냐하면 블록이 네트워크를 통해 전파되기 위해서는 일정 시간이 필요하기 때문이다. 예를 들어 마이너 A가 블록을 채굴하고, 이 블록이 전파되기 전에 마이너 B가 블록을 채굴한 경우, 마이너 B의 블록은 버려지고(stale) 이는 네트워크의 안정성에 기여하지 못한다. 또한 이는 중앙화 이슈가 있는데, 만약 마이너 A가 마이닝 풀이며 30%의 해시 파워를 가지고 있고, 마이너 B가 10%의 해시 파워를 가진다면, A는 전체 시간 중 70%의 시간에서 stale 블록을 생성할 가능성이 있지만 (30%, 자신이 생성한 블록은 즉시 전파될 것이므로) B는 90%의 시간동안 리스크를 가지게 된다.

그러므로 블록 인터벌이 높은 stale rate를 가지게 될 만큼 짧다면 A는 실질적으로 단순히 그 규모로 인해 훨씬 효율적이 될 수 있다. 이런 이유로 블록을 빠르게 생성하는 블록체인은 많은 해시 파워를 가진 한 마이닝 풀이 마이닝 프로세스에 있어서 실질적인 통제력을 가지게 될 수 있다.

GHOST는 stale block을 어느 체인이 가장 긴 가를 결정하는 계산에 포함시킴으로써 첫 번째 이슈를 풀고 있다. 즉 블록의 부모와 그 조상들 뿐만아니라, 블록 조상의 stale decendants도 포함시키는 것 (Ethereum에서는 “uncle”이라 부름)

중앙화 이슈의 경우 이더리움에서는 다른 전략을 적용하는데, 블록 보상을 stale 에도 지급한다. stale block은 기본 보상의 7/8 (87.5%)를 지급하고, stale block을 포함하는 nephew는 1/32(3.125%)를 포함 포상금으로 지급한다.  트랜잭션 수수료는 uncles나 nephews에게 지급되지 않는다.

이더리움에서는 stale block으로 7번째 세대까지만 포함된다. 무한히 늘릴 경우 계산이 너무 복잡해지고, 마이너로 하여금 메인 체인에서 채굴할 동기를 없애기 때문이다. 시뮬레이션 결과 7번째 레벨로 제한하는 것이 지나치게 큰 부정적 결과 없이 원하는 결과를 끌어낼 수 있는 것으로 확인되었다.

블록 시간 알고리즘에 대한 결정은 다음의 내용을 포함한다.

  • 12초의 블록 시간: 12초는 네트워크 레이턴시보다는 충분히 크면서 가능한 빠른 인터벌 시간이다
  • 7 블록 조상 제한: 이 것은 블록 히스토리를 일정 수의 블록이 지난 후에 “잊을 수 있도록” 하기 위함이다. 7 블럭은 원하는 효과를 얻기에 충분하다.
  • 1 블록 자손 제한: 단순성 목표를 위한 설계.
  • Uncle 유효성 요구사항: Uncle들은 유효한 헤더를 가지고 있어야 함.
  • 보상 분배 : Uncle의 경우 기본 보상의 7/8. Nephew의 경우 1/32. 둘 다 트랜잭션 수수료는 없음

Difficulty Update Algorithm

diff(genesis) = 2^32

diff(block) = diff.block.parent + floor(diff.block.parent / 1024) *
    1 if block.timestamp - block.parent.timestamp < 9 else     -1 if block.timestamp - block.parent.timestamp >= 9

설계 목표

  1. 빠른 업데이트: 블록간의 시간은 해시파워에 따라 빠르게 업데이트 되어야 함
  2. 낮은 변동성: 난이도는 해시파워가 균등할 경우 급격히 바뀌지 않아야 함
  3. 단순성: 구현이 상대적으로 단순한 알고리즘이어야 함
  4. 낮은 메모리: 알고리즘은 몇 블럭 이상의 이력에 의존해서는 안 되며, 가급적 적은 “메모리 변수”를 포함해야 함
  5. Non-exploitability: 알고리즘은 마이너로 하여금 타임스탬프를 조작하거나 마이닝 풀이 수익 극대화를 위해 반복적으로 해시 파워를 추가하고 제거하는 일을 장려해서는 안 됨

Gas and Fees

비트코인에서의 모든 트랜잭션은 거의 비슷하고, 네트워크상의 비용이 하나의  단위로 계산될 수 있는 반면에, 이더리움에서의 트랜잭션은 훨씬 복잡함. 그렇기 때문에 트랜잭션 수수료 시스템은 다양한 요소들을 고려해야 함.  특히 중요한 것은 이더리움의 프로그래밍 언어가 튜링 컴플리트하다는 점인데, 그렇기 때문에 하나의 트랜잭션의 네트워크 대역, 스토리지, 연산 사용량은 달라질 수 있음. 또한 무한 루프를 통한 DoS 공격을 막는 것 역시 중요한 목표임

기본 메커니즘

  • 모든 트랜잭션은 사용할 의사가 있는 “gas”의 양(startgas)과 단위 gas당 지불할 가격(“gasprice”)을 반드시 지정해야 함. 실행이 시작될 때 startgas * gasprice 만큼의 이더가 트랜잭션 발신자의 계좌에서 삭제됨
  • 데이터베이스를 읽고 쓰고, 메시지를 보내고, 가상머신에서 실행되는 모든 연산 단계와 같은 트랜잭션 실행 중의 모든 오퍼레이션은 일정량의 gas를 사용함
  • 트랜잭션이 완전히 종료되었는데 지정된 한도보다 적은 gas를 사용한다면, 남은 gas(gas_rem )는 트랜잭션 종료와 함께 트랜잭션 발신자에게 반환됨. 그리고 블록의 마이너는 (startgas -gas_rem) * gasprice 만큼을 보상으로 방게 됨
  • 만약 트랜잭션 실행 도중에 gas가 떨어지게 되면 모든 실행은 원상복귀됨. 하지만 트랜잭션 자체는 그럼에도 불구하고 유효하며 그 결과로 전체 startgas * gasprice만큼의 이더는 마이너에게 지급됨
  • 컨트랙트가 다른 컨트랙트에 메시지를 보내는 경우, 이러한 sub excution을 위해 gas limit을 설정할 수 있음. 만약 sub-execution 실행 중 gas가 떨어지면 sub-execution은 원상복원되지만 gas는 그래도 소비됨

gas cost 관련

  • 21000 gas는 모든 트랜잭션의 기본 요금. 이 값은 서명에서 발신자 주소를 복원하는 elliptic curve 연산과 트랜잭션의 저장을 위한 디스크와 네트워크 비용임
  • 트랜잭션은 무제한의 데이터를 가질 수 있음. 데이터에 대한 비용은 값이 0인 바이트의 경우 4 gas, 그렇지 않은 경우 68gas.
  • SSTORE 연산 (값을 계정의 스토리지에 보관)은
    • 0인 값을 0이 아닌 값으로 변경할 때 20000gas
    • 0에서 0으로, 또는 0이 아닌 값에서 0이 아닌 값으로 변경시 5000gas
    • 0이 아닌 값에서 0으로 변경시 5000gas
    • 성공적으로 트랜잭션 실행이 종료되면 20000gas 환불 (환불은 사용한 전체 gas의 50% 캡을 가지고 있음. 트랜잭션의 전체 소모 gas가 30,000이면 15,000이 환불 한계)
  • 컨트랙트가 제공하는 데이터의 메시지에는 과금 없음
  • 메모리는 무한히 확장 가능한 배열임. 하지만 32바이트당 1gas가 소모됨
  • 인자의 값에 따라 연산 시간이 크게 달라지는 opcode의 경우 (예: EXP) 인자의 값에 따라 소모되는 gas가 달라짐

Virtual Machine

Ethereum Virtual Machine은 트랜잭션 코드가 실행되는 엔진임. 그리고 이더리움을 다른 시스템과 차별화하는 요소이기도 함.

설계 목표

  • 단순성: 가급적 적은, 그리고 가급적 로우레벨 opcodes, 가급적 적은 데이터 타입
  • 전체적인 확정성: VM 스펙의 어느 부분에도 모호한 부분이 없어야 함. 또한 연산 스텝에 대한 정확한 개념이 조재해야 함. 이 스텝들로 개스 소모를 계산하게 됨
  • 공간 절약: EVM 어셈블리는 가능한 컴팩트 해야 함 (예: 기본 C 프로그램의 4000byte 기본 사이즈는 받아 들일 수 없음)
  • 특화 기능: 20바이트 주소 처리, 32바이트 값을 가지는 암호화 처리 능력, 커스텀 암호화에 사용되는 모듈러 연산, 블록과 트랜잭션 데이터 읽기, 상태 정보 확인 등
  • 단순한 보안: gas cost 모델에 대응하기 쉬워야 함. 이는 VM을 non-exploitable하게 만듦
  • 최적화 용이성: 최적화를 적용하기 쉬워야 함. JIT 컴파일되거나 다른 속도를 향상시키 버전의 VM이 만들어 질 수 있도록.

주요 설계 결정

  • 임시/영구 스토리지 구분: 임시 스토리지와 영구 스토리지의 구분이 존재함. 임시 스토리지에 저장된 값은 해당 인스턴스 안에서만 유효. 영구 스토리지는 해당 컨트랙트 전체에 유효.
  • 스택/메모리 모델: 세 가지 유형의 computational state를 가짐. Stack/ Memory / Storage.
  • 32바이트의 워드 크기: 비트코인의 경우 제한이 없음. 4, 8바이트 워드 등은 너무 작아 비효율적임. 32바이트는 많은 크립토 구현에서 사용되는 값들을 보관하기에 충분함. 반면 제한이 없는 경우 안전한 gas model을 만들기가 어려움
  • 독자적인 VM을 개발: JAva, Lisp dialect, Lua 등을 사용할 수도 있겠으나 독자적인 VM을 만들기로 결정함. 그 이유는 다음과 같음
    • 요구되는 VM spec이 다른 것들에 비해 훨씬 단순함
    • VM을 요구사항에 맞게 좀 더 특화시킬 수 있음. 예: 32byte word size
    • 복잡한 외부 dependency를 없앨 수 있음. dependency가 많아지면 설치가 매우 어려워짐
  • 가변적인 메모리 크기
  • 스택 크기에 제한 없음
  • 1024 call depth 제한
  • 타입 없음: 단순성을 위해. 대신 signed, unsigned에 대응하는 별도의 opcode 제공 (예: DIV, SDIV, MOD, SMOD)

댓글 남기기