블록체인에 대한 조금 더 자세한 설명

최근 가상 화폐와 블록체인에 대한 열기는 정말 핫하다. 관련된 세미나를 가 보면, 주제에 관계 없이 항상 만원 사례를 이루는 경우가 많다. 기술을 논하는 세미나에서 이더리엄의 가격 동향에 대한 질문이 나오거나 하는 일은 매우 흔해졌다.

또한 블록체인에 대해 설명하는 기사와 아티클들 역시 매우 많아졌다. 그런데 그런 기사들을 보거나, 관련되어 누군가와 이야기를 하다 보면 꽤 잘못 이해하고 있는 경우가 상당히 많이 보인다. 이런 현상은 많은 글들이 독자들이 가상화폐나 블록체인에 대해 잘 모른다는 가정 하에, 글을 매우 단순화시키기 때문이기도 하고, 글을 쓰시는 분들도 정확히 이해하지 못 하고 있기 때문으로 보이기도 한다. 그래서 현 시점에서 아는 한도 내에서 좀 더 정확하게 이야기 해 보고 싶어졌다.

일단 자주 보이는 오류 중 하나는 블록체인 = 비트코인 이라는 관점에서 쓰이는 글들이다. 대부분 신문 기사들에서 이런 경향이 보이는데, 기반 기술과 서비스가 혼용되면서 비트코인에만 해당하는 내용이 블록체인 전체에 관한 것으로 쓰이는 경우가 매우 많다.

최근 블록체인은 정말 급격한 속도로 새로운 개념들이 나타나고 있기 때문에, 모두 같은 방식으로 동작할 것이라고 믿는 것은 많은 오해를 낳는다. 그렇기 때문에 이 글에서는 각각의 오해에 대해 좀 더 구체적으로, 그리고 대상을 가급적 명확하게 적어보려 한다.

1. ’10분’마다 새로운 블록이 생성된다

이 이슈는 기사들에서 가장 많은 오류가 나타나는 경우 중 하나다. 블록의 생성 시간은 절대적으로 정해져 있는 것이 아니며, 통계적으로 어느 정도의 시간을 유지하도록 설계되어 있을 뿐이다. 이더리움의 경우 12초인데, 이 역시 정확히 12초마다 생성된다는 뜻이 아니다. 주로 퍼블릭 블록체인들 (가상화폐를 지향하는)에서 설정되는 것이며, 프라이빗 체인의 경우 아예 정해진 시간이 없는 경우들도 있다.

비트코인이나 이더리움과 같은 PoW(Proof of work) 기반의 블록체인들은 각각의 노드가 블록을 생성하기 위해서 일종의 문제를 풀어야 하는데, 문제의 난이도를 지속적으로 변화시키면서 전반적인 블록 생성 시간을 조정한다. 너무 빨리 블록들이 생성되면 난이도를 높이고, 너무 인터벌이 길어지면 난이도를 내리는 방식이다. 이를 통해 평균적으로, 최초에 설계된 생성 시간의 텀을 가지도록 유도한다.

이런 인터벌이 필요한 이유는, 체인이 둘로 나뉘어지는 경우를 최소화 하기 위함이다. 예를 들어 한 노드가 블럭을 ‘채굴’하고, 그 블럭을 전 세계에 흩어져 있는 노드에 전파하기 위해서는 어느 정도 시간이 필요하다. 이더리움의 경우 테스트를 통해 12초 정도면 모든 노드가 전달을 받을 수 있다고 판단하고 결정한 결과이다. 이 시간이 더 짧아지면, 미처 모든 블럭에 채굴 사실이 전파되기 전에 다른 노드가 채굴에 성공하여 체인이 나뉘고 경쟁 상태로 들어가는 일이 빈번해지게 된다.

PoS(Proof of stake)기반의 가상화폐의 경우, 난이도와 같은 개념은 없다. 카르다노SL (Settlement Layer)의 경우, 향후의 타임 슬롯에 어느 노드가 블록을 생성할 것인지를 동전 던지기와 같은 프로토콜을 이용해서 미리 결정해 둔다. 하지만 이 경우 해당하는 타임 슬롯에 해당 노드가 없을 수도 있기 때문에, 블록을 전파할 시간이 충분하지 않으면 역시 체인이 나뉘어질 가능성이 있다. 그렇기 때문에 블록 생성 시간이 필요하다.

2. 전체 노드의 과반이 동의해야 블록에 저장된다

이 말은 반쯤 맞다고 보는게 옳을 것 같다. 트랜잭션을 생성하고, 컨센서스를 확보하고, 블록을 생성하는 과정은 적용된 알고리즘에 따라 달라진다. 또한 이러한 워딩은 마치 하나의 트랜잭션에 대해 노드간에 많은 대화와 확인 과정이 존재할 것처럼 들리기도 하는데, 이 역시 알고리즘에 따라 조금씩 다르다.

예를 들어보자. 이더리움의 경우 누군가 트랜잭션을 생성하면, 이 트랜잭션은 즉시 모든 노드에 전파되고 각 노드가 관리하고 있는 트랜잭션 풀에 들어간다. 모든 계정의 잔고는 월드 스테이트에 보관되어 있기 때문에, 각각의 노드는 해당 트랜잭션이 정상적인지 아닌지를 개별적으로 판단한다. 그리고 마이닝을 수행하는 노드들은 이렇게 쌓인 트랜잭션 중, 자신이 만드는 블럭에 집어 넣을 트랜잭션들을 정해 둔 상태에서 (수수료를 많이 주는 트랜잭션 우선으로) 자신이 문제를 풀 경우 해당 해시값을 블록에 포함시켜, 블록을 전파한다.

이렇게 전파된 블록은 각 노드에서 역시 유효한 것인지를 확인하게 되고, 유효하다면 체인에 저장하고, 해당 블록에 포함된 트랜잭션들을 자신의 트랜잭션 풀에서 삭제하고, 새로운 문제를 풀기 시작한다. 즉, 블록의 선언은 단 한 번 이루어지며 서로 이게 맞네 아니네 논의하지는 않는다. 각자 보관해 둔 정보를 기반으로 각자가 판단하는 것이다.

이더리움의 월렛을 보면 Confirmation이라는 부분이 있는데, 예를 들어 하나의 트랜잭션이 50개의 Confirmation을 받았다라는 식으로 표시된다. 이는 ’50개 노드가 이게 맞다고 응답했다’라고 해석될 소지가 큰데, 실제 의미는 이 트랜잭션이 블록에 포함된 이후 50개 블럭이 그 블럭 뒤에 추가되었다라는 의미일 뿐이다. 트랜잭션 자체가 유효하냐 아니냐는 월드 스테이트를 보고 각자 판단한다.

그렇다면 이 컨퍼메이션이 의미하는 것은 무엇인가? 앞서 언급했듯이 블록들은 형성 과정에서 분기가 이루어질 수 있다. 이 경우 좀 더 긴 블록을 가진 쪽이 이기게 되는데 (longest chain wins), 진 체인에 속한 트랜잭션은 다시 트랜잭션 풀로 돌아가게 되고, 유효성 검사를 거치게 된다. 즉 컨퍼메이션이 충분히 크다는 것은, ‘이 트랜잭션은 충분히 오랫동안 변동 없이 블럭에 보관되었기 때문에, 취소될 가능성이 적다’라는 것을 표시하는 것이다.

사실 악의적인 노드의 경우, 정당한 블록이 도착했음에도 불구하고 해당 블록을 기록하길 거부한 후 체인 길이 경쟁에 나설 수도 있다.

BFT알고리즘을 사용하는 경우는 이와는 다르다. 각각의 트랜잭션에 대해 노드들이 응답을 서로 주고 받으며, 확인하는 과정을 거친다.

3. 블록체인은 보안성이 높다

간혹 이야기를 하다 보면, 블록체인은 보안성이 높다라는 말이 오해를 불러 일으켜 마치 최강의 보안 솔루션처럼 생각하시는 분들도 보게 된다. 사실 이 부분은 지금도 계속 변화와 개선이 일어나고 있는 부분이라 쉽게 말하기는 어렵지만, 조금 과도한 신뢰가 부여되고 있지 않는가 하는 생각이 들기는 한다.

기본적으로 비트코인, 이더리움과 같은 퍼블릭 체인이 제고하는 보안성은 단 한 가지이다. 기록의 변조가 거의 불가능하다라는 점이다. 그리고 오래된 기록일수록 변조는 더욱 어렵다. (추가적으로 비밀키/공개키만 사용되는 시스템이기 때문에, 개인 정보가 노출되지 않는다는 점도 추가하자면 할 수는 있겠다)

그리고 이 위변조 방지는, 집단의 힘에서 나온다. 즉 기술 자체가 무지무지하게 보안성이 높아서 위변조가 안 되는 것이 아니라, 같은 내용을 공유하는 ‘정상적인’ 참여자가 많기 때문에 이 것이 가능해지는 것이다. 사실 사용되는 기술 요소로 보면 PKI, HASH 정도인데, 이건 블록체인 아닌 어느 시스템에서도 쓸 수 있고, 많이 쓰이는 기술이다.

사실 그 외적인 면에서는 크게 다를 것이 없다. 모든 데이터가 공유되기 때문에, 특정 주소의 잔고나 거래내역 같은 것은 모두 확인된다. 어찌 보면 프라이버시가 좀 더 많이 노출되는 면도 없지 않겠다. (비트코인, 이더리움의 경우)

물론 이런 문제들을 HD (Hierarchically deterministic) Wallet을 도입한다거나, Dash나 PIVX처럼 마스터 노드를 통한 익명화를 제공한다거나, Zcash의 경우 영지식 증명을 도입한다거나 하는 식으로 가상화폐의 보안성을 높이고 있고, Corda의 경우 관계 없는 노드와는 아예 트랜잭션을 공유하지 않는 식도 도입되고 있다. 이런 노력들로 인해 보안성/프라이버시는 지속적으로 나아지고 있기는 하지만, ‘블록체인을 도입하면 데이터는 절대 안전하다’라는 식으로 생각해서는 곤란하다.

4. 블록체인은 ‘분산’ 시스템이다

이 부분은 사실 번역으로 인한 문제인 듯 한데, 흔히들 ‘distributed ledger’라고 부르고 ‘분산 장부’라고 번역을 해 버린다. 이 경우 번역은 문제가 없는데 ‘분산’이라는 의미가 기존에 흔히 이야기 되던 의미와 다르기 때문에 오해가 생기는 경우가 있었다.

블록체인에서의 ‘분산’은 각 참여 노드가 자신의 판단에 근거하여 컨센서스를 이루고 데이터를 기록하며, 동일한 데이터가 여러 노드에 저장된다라는 의미인데, 의외로 많은 분들은 이를 기존에 주로 사용하던 ‘로드의 분산’ 또는 데이터의 ‘분할 저장’으로 해석해 버린다.

한 예로 IoT 기업에 근무하시던 분인데, 수 많은 센서에서 기록되는 값을 블록체인이 ‘분산’하여 ‘위변조를 방지’해 줄 수 있을지를 문의하신 적이 있다. 음.. 조금 당혹스러웠다.

다양한 블록체인들이 나오고 있고, 스케일러빌리티 확보를 위한 샤딩 역시 중요한 토픽 중 하나이기 때문에, 조만간 전통적 의미의 ‘분산’ 시스템이 될 가능성은 존재한다. 하지만 일반적으로 이야기되는 퍼블릭 블록체인들은 전혀 다른 의미의 ‘분산’이라는 것을 기억해 두는 것이 좋을 것 같다.

 

생각나는 대로 몇 가지 적어 보았는데, 쓰고 보니 갑자기 몇 달 후면 이 글도 낡고 오류 투성이인 글이 될지도 모른다는 불안감이 급격히 들기는 한다. 정말 정신 없이 바뀌고 있으니. 그래도 현 시점에서는 도움이 될 지도 모른다는 기대를 가져 본다. =)

Under-Optimized Smart Contracts Devour Your Money

얼마 전에 흥미로운 논문을 보게 되었다. https://arxiv.org/abs/1703.03994

논문의 제목은 이 글의 제목과 같은데, 간단히 요약하자면 컨트랙트를 코딩하고 EVM에서 동작하도록 컴파일 될 때 개스 사용이 최적화 되지 않고 있다는 이야기다. 즉, 코딩을 어떻게 하느냐에 따라 개스 소모가 달라지고, 이런 부분은 실제 Solidity로 개발을 하는 개발자는 확인을 하기가 어려우니 컴파일러의 최적화 기능을 강화해야 한다라는 것이다. 5페이지짜리 짧은 페이퍼이고, 매우 Plain English로 쓰여 있으니 부담없이 읽을 만 하다.

Ethereum의 스마트 컨트랙트에 친숙하지 않은 분을 위해 약간의 부연 설명을 하자면,  Ethereum에서는 스마트 컨트랙트를 (현재) Solidity로 작성하며, 이는 컴파일되어 EVM(Ethereum Virtual Machine)코드로 변환된다. 실행 시 컨트랙트는 Miner의 EVM에서 동작하게 되며, 이 때 gas를 사용하게 된다. 즉, 누군가의 컴퓨팅 자원을 사용하니 댓가를 지불해야 한다는 것으로 쉽게 이해할 수 있겠다.

또한 gas는 DoS 어택을 막는 용도로도 중요한 역할을 한다. 간단히 예를 들어 컨트랙트에 무한 루프를 넣고 무거운 동작을 반복하게 만든다면 노드의 실행에 장애가 생길 수 있는데, 각 Operation에 비용을 지불하게 함으로써 이러한 공격을 막게 된다. (만약 해커가 만수르라면??)

EVM의 동작에는 소모되는 gas가 설정되어 있는데, 이는 Yellow Paper의 Appendix G에서 확인할 수 있다. 해당 표는 아래와 같다.

스크린샷 2017-04-12 11.02.31

대충 살펴 보면 굉장히 싼 오퍼레이션이 있는 반면에, 매우 비싼 것들이 존재한다. 대개 스토리지를 사용하거나, 계정을 생성하는 것들이 그렇다. 그렇다면 개발할 때에도 가급적 gas를 적게 사용하도록 만들어야 할 것이다. (버튼 한번 누를 때마다 1,000원씩 날아간다면 무서워서 쓰겠나..)

다시 논문의 내용으로 돌아오면, 저자들은 2가지 분류의 7가지 정도의 개스를 낭비하는 패턴을 찾아 냈다. 그리고 이런 부분은, EVM의 동작을 깊이 이해하지 못하는 개발자들이 파악하기 어렵기 때문에 컴파일러에서 처리를 해 주어야 한다고 이야기 하고 있다.

첫 번째 분류인 Useless Code에서는 두 가지 패턴을 이야기 하고 있는데, dead code와 opaque predicate이다.

스크린샷 2017-04-12 11.12.35.png

어찌 보면 이 부분은 간단하다. 왼쪽의 패턴 1에서는 4행의 코드는 실행될 일이 없다. x는 5보다 크기 때문에, 이 값을 제곱한 값이 20보다 작을 일은 없기 때문이다. 우측 패턴의 경우는 불필요한 조건문이 들어간 경우다. 3행은 의미가 없다. 하지만 gas를 사용한다.

두 번째 분류는 Loop Related Patterns인데, 이는 좀 더 심각하다. 말 그대로 루프를 통해 반복되기 때문에 불필요한 비용 낭비가 급격히 올라갈 수 있기 때문이다.

스크린샷 2017-04-12 11.16.52

패턴 3은 비싼 오퍼레이션이 루프에 들어가 있는 경우다. sum은 스토리지에 보관되는 변수인데, SLOAD를 통해 스택에 올라가고, SSTORE를 통해 값을 저장한다. SLOAD는 200 gas를, SSTORE는 20,000 gas(!)를 사용한다. 값 비싼 오퍼레이션은 루프 밖으로 빼야 하고, 임시적으로 사용되는 값들은 memory 를 사용하는 편이 싸게 먹힌다.

패턴 4는 루프의 결과가 항상 상수가 나오는 경우다. 3,4행의 결과로 sum은 항상 5050이 된다. 즉 return sum; 보다는 return 5050; 으로 하고 루프를 없애는게 낫다.

스크린샷 2017-04-12 11.17.37

패턴 5는 동일한 역할을 하는 루프라면 루프를 합치라는 이야기다. 4,6행에서 시작하는 두 개의 루프는 동일한 횟수만큼 반복하는데, 하나의 루프로 합쳐서 오퍼레이션의 수와 bytecode 사이즈를 줄이라는 이야기다.

패턴6은 반복되는 연산을 빼라는 것이다. 6행에 보면 sum + x + y가 있는데, 여기서 x + y는 루프 안에 있을 필요가 없다. x + y를 반복 계산하기 위해 SLOAD가 반복 호출되는 결과를 가져 온다.

스크린샷 2017-04-12 11.17.47

패턴 7은 비교문의 결과를 컴파일 시에 알 수는 없지만, 루프 내에서 항상 동일한 결과를 가질 경우 루프 바깥으로 빼라는 것이다. 3행의 x > 0 는, x 값에 따라 참 또는 거짓이겠지만 루프를 반복할 대 항상 동일한 값을 가지게 된다. 이런 경우 루프를 들어가기 전에 미리 조건을 검사하는 것이 유리하다.

사실 이러한 패턴들은 어찌 보면 상당히 사소한 것들처럼 보이고, 누가 저렇게 할까 싶은 것도 있기는 하다. 하지만 사소하기 때문에 의외의 엄청난 결과를 가져올 위험성도 내포하고 있다.

저자들은 GASPER라는 툴을 만들어 2016년 11월 5일 까지 Ethereum 네트워크에 등록된 모든 컨트랙트를 전수 검사를 했다. 전체 수는 566,907이지만 코드가 없는 것, 중복된 것 등을 모두 제외한 4,669 컨트랙트를 대상으로 실시했는데 상당히 놀라운 결과가 나왔다.

스크린샷 2017-04-12 11.37.16.png

패턴 1, 2 (불필요한 코드)가 전체 컨트랙트의 93.5%, 90.1% 에서 발견되었고, 루프 내부에 비싼 오퍼레이션을 포함하는 경우도 전체의 80%에 달했다. 이 세 가지 모두를 포함하고 있는 경우는 71.7%.

즉 이런 낭비를 만드는 것이 매우 쉽다는 것을 의미하는 결과라고 본다. 특히, 그 동안 서버의 스펙이 높아져 가면서 사실 코드 자체의 최적화에 대한 고민은 상대적으로 덜 중요해져 왔다고 생각하는데, Solidity로 개발할 때는 (최소한 당분간은) 주의가 필요하다.

특히 아직 표준 라이브러리가 명확하게 구성되어 있지 않고, 데이터의 보관을 외부 DB등에 사요하는 것에 익숙해져 있는 경우가 많은데 Solidity에서는 사용 불가능하기 때문에 이런 낭비에 대해 좀 더 고민할 필요가 있다고 하겠다.

마지막으로 http://dapps.ethercasts.com/  을 살펴 보다가 발견한 것인데, 아마도 베트남 사람으로 보이는 친구가 출/퇴근 펀치 카드를 간단히 구현한 것이 있었다.  재미 있는 것은 개략적인 사용료인데, gas price = 0.00000002 Ether, 1 Ether = $40으로 가정했을 때의 월 사용료가 $343.2에 달한다는 것이다. (관리자 10명, 직원 100명, 직원은 하루에 두 번 카드를 체킹한다는 가정)

아직은 막 시작된 분야이기 때문에 비즈니스 모델을 고민하기에 이른 감이 없지 않지만, 저렇게 비싼 비용을 지불하고라도 사용할만한 이익을 제공하기가 쉽지는 않아 보인다.

 

 

RS Coin – 중앙 은행을 위한 블록체인

가상 화폐는 다양한 영역에서 관심을 보이고 있는 기술입니다. 최근 비트코인의 가격 급등으로 인해, 관심을 가지는 분들이 점점 늘어나고 있는 것 같습니다.

퍼블릭 블록체인에 기반한 가상 화폐들은 분명히 그 나름의 강점들이 있습니다. 프라이버시는 최대한 보호되고, 정부의 정책 등으로 나의 재산권을 제약 받지 않을 수 있는 점 등이죠. 반면에 여전히 변동성이 매우 높다는 점은, 그리고 그런 변동이 자신이 살고 있는 국가의 범위 밖에서 일어난 일에 기인한다는 것은 실 생활에서 사용되기에 어려운 점이라고 볼 수 있을 겁니다.

최근에는 중국, 영국을 포함한 많은 국가의 중앙 은행들이 블록체인 기반의 가상 화폐를 실험하고 있습니다. 다만 비트코인, 이더리움과 같은 기존의 가상화폐들과 다른 점이라면, 중앙 은행이 통제할 수 있는 형태의 설계를 하고 있는 것이죠. 이를 통해, 통화 정책, 금융 정책 등을 기존과 같이 적용할 수 있으면서도 더 높은 수준의 투명성을 확보하길 기대하는 것입니다.

중국 인민 은행의 경우 독자 개발한 디지털 화폐를 이용하여 최근 실험을 진행한 것으로 알려지고 있습니다(링크).  영국 중앙 은행의 경우 2014년부터 연구를 해 오고 있는데, 2015년에는 George Danezis 등이 RS Coin을 제안하게 됩니다. 논문은 여기서 확인할 수 있습니다.

RS Coin은 기존 Bitcoin이 가지고 있던 확장성의 문제를 해결해야 할 가장 큰 문제로 보고 있습니다. 초당 7건 (실제로는 더 낮은)의 TPS로는 대량의 거래를 감당할 수 없기 때문입니다. 이를 해결하기 위한 방법으로 중앙은행과 중앙 은행이 신뢰할 수 있는 기관들을 mintette으로 나누고, 일종의 샤딩을 적용하는 방법을 제안하고 있습니다.

예를 들어 전체 키 스페이스를 10개로 나누고, 각 스페이스 마다 3개씩의 Mintette를 할당하는 그림을 생각해 볼 수 있습니다. 이 경우 각 mintette들은 할당된 키 스페이스에 해당하는 트랜잭션을 원장에 보관하고 이를 lower-level block라고 부릅니다.

이렇게 생성된 각각의 lower-level blocks은 중앙 은행으로 보내지고, 중앙 은행은 이를 모아 하나의 마스터 체인, higher-level block을 만드는 방식입니다. 사실 분산 시스템에서 쉽게 볼 수 있는 형태이죠. 그리고 이러한 구조 때문에, 스페이스를 더 많이 분할하고 mintette을 늘릴 수록, 전체 퍼포먼스는 쉽게 올라갑니다. 논문을 보면 Python으로 구현한 프로토타입에서 2,000 TPS 이상을 기록했다고 이야기 하고 있습니다.

물론 화폐의 발행은 중앙은행만 할 수 있습니다. 비트코인처럼 채굴해서 코인을 얻는 것은 가능하지 않죠. 그리고 그 것은 중앙은행이 가지는 성격을 생각해보면 당연하다고 말할 수 있겠습니다.

이런 중앙 집중적 구조의 가상 화폐에 대한 비판도 많이 있습니다. 단순히 종이에 찍어낼 것을 버튼 하나로 가능하게 만드는 것 외에는 다른 것이 없다는 이야기도 있죠. 하지만 한 편으로, 국가의 경계가 사라지고 전 세계가 하나 되는 이상향이 될 수 없는 한, 통화가 보편적으로 사용되기 위해서는 각 국 정부의 개입은 어떤 형태로든 피할 수 없는 것이 아닌가 생각되기도 합니다.

저희 회사의 파트너인 IOHK는 RS Coin을 구현하여 공개한 바 있습니다. 발표 영상은 아래에서 볼 수 있습니다. 관심 있으신 분은 살펴 보셔도 좋겠습니다.

이더리움 설계 근거

원문: 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)