WASM과 가상머신, 블록체인을 위한 특수화

Jiyong Ha(Brew)
RIZON Korea
Published in
16 min readMar 7, 2020

많은 블록체인 플랫폼 프로젝트들은 스마트 컨트랙트에 대한 정의를 구현할 때 사용성과 확장성을 고려해 컨트랙트 작성을 위한 프로그래밍 언어, 그리고 이를 실행할 수 있는 샌드박스 가상머신 개념을 도입합니다.

예를 들어 이더리움의 경우 Solidity와 함께 EVM이 있습니다. 때때로 Solidity와 EVM을 서로 필수적인 관계로 오해할 수 있으나, 정확하게는 EVM의 입력은 바이트코드로 Solidity와 직접적인 관계를 가지지 않습니다. EVM은 같은 스팩의 바이트코드를 생성할 수 있다면 여느 언어든 가능합니다. 예를 들어, EVM 바이트코드를 만들 수 있는 다른 언어로는 Vyper가 있습니다.

이더리움은 2.0을 기획하면서 EVM을 대체할 WASM 기반의 가상머신인 EWASM의 기획을 공개하며 모델의 변화를 예고했습니다. 뿐만 아니라 근래의 여타 플랫폼 프로젝트들도 WASM을 도입하는 것이 유행처럼 번지고 있습니다.

이 글에서는 블록체인에서의 차기 모델로 거론되는 WASM 기반의 가상머신과 진보 시도의 흐름, 그 과정 중 블록체인에서의 특수한 골칫거리에 대해서 생각을 나누려 합니다. 만약 언급한 언어와 가상 머신 그리고 바이트코드 등 컴퓨터 실행의 일반적인 개념에 대한 이해가 필요하시다면, 검색을 통해 친절하고 쉬운 설명들을 금방 찾아 볼 수 있습니다.

WebAssembly의 용도

WASM은 WebAssembly의 약자입니다[1]. 이름에서 짐작할 수 있듯, Web-웹 환경에서 Assembly-네이티브 어셈블리에 근접한 성능을 목표로 하는 표준 코드 스팩입니다. Assembly란 대게 CPU가 이해할 수 있는 OPCODE 바로 직전, 이를 사람이 읽고 작성할 수 있는 최종 수준 언어를 의미합니다. 즉, 서로 이질적으로 느껴지는 상위 레이어 웹과 코어 레이어 네이티브, 두 구간을 연결하기 위한 최소한의 중간 단계를 정의하는 것을 목표로 합니다.

사실 16진수로 구성된 OPCODE도 작성이 불가능하지는 않습니다. 인라인 후킹, 스택 제어, 레지스터 제어 등 다양한 저수준의 특수한 접근이 필요할 때 짧은 OPCODE를 직접 작성하기도 합니다.

그렇다면 WASM을 컴파일한 바이너리는 CPU가 직접 이해할 수 있는 OPCODE 명령어로 이루어져 있을까요? 그렇지는 않습니다. 이 역시 특정 CPU 아키텍처 스팩 명령어가 아닌, 변환하기에 적절한 가상 명령어 중간코드(IR)로 이루어져 있습니다. 그래서 많은 소개 문서에서 ‘네이티브에 근접한’이라는 표현을 사용하며, 스팩에서도 Virtual ISA(Instruction-Set-Architecture)라 정의하고 있습니다[2].

결국 WASM도 IR을 실행 환경의 CPU 명령어로 변환하거나, 인터프리팅 방식으로 해석하고 실행해 줄 가상머신이 필요합니다. 이를 Java-JVM과 비교한다면 가상머신이 가지고 있는 기본 기능은 비슷하지만 매우 다른 부분도 있습니다. WASM은 네트워크 환경에서 IR을 스트리밍 받는 동시에 부분적으로 병렬 컴파일을하고 실행하기 위해 설계되었습니다. 또 웹 컴퓨팅에 더 적합하도록 여러가지 다른 설계 원칙이 더 있습니다. 그 중 한가지는 샌드박스 실행 환경으로, 파일 I/O, 메모리 I/O 등 실제 실행하는 호스트 환경에 영향을 미칠 수 있는 접근을 제한해 보안 위협을 방지합니다.

그렇다면 WASM이 웹과는 다른 블록체인 플랫폼에서 다루어지는 이유는 무엇일까요? 몇 가지 간단한 실제 사용 사례를 살펴보며, 어떤 특징 때문에 WASM이 블록체인 플랫폼에서도 사용되는지 알아보고자 합니다.

웹에서 고성능 기능을 구현하기 위한 사례

Google Earth

웹에서 복잡한 3D 렌더링이 필요한 경우, 대게 추가 프로그램을 설치하는 등의 방식을 사용했습니다. 하지만 WASM의 하드웨어 그래픽 제어-OpenGL 지원을 활용해 웹에서도 추가 설치 없이 3D 렌더링을 지원하게 되었습니다. 구글어스는 이를 활용한 대표적 서비스입니다.

네이티브 프로그램을 웹 프로그램으로 이식하기 위한 사례

Qt for WebAssembly

QT는 C++ Cross platform UI Framework이며, 여러 OS에서 하나의 코드로 같은 GUI 프로그램을 편리하게 개발하기 위해 사용합니다. 최근 QT는 GUI 웹 프로그램을 지원하기 위해 WASM 프레임워크를 한발자국 내딛었습니다. 아직까지는 이식성 등 실험적인 수준이지만, 웹에서도 고성능 GUI 프로그램을 위해 WASM을 이용할 수 있는 가능성을 보여줍니다.

Malware(악성코드)

사용자 몰래 사용자의 컴퓨터를 이용하여 암호화폐 채굴을 시도하는 악성 프로그램이 있습니다. 이런 악성 채굴 프로그램이 WASM과 Emscripten을 이용하게 되면서 설치 없이 웹페이지에 접속하는 것 만으로 채굴을 시도하는 수준까지도 발전하였습니다. 물론 WASM과 가상머신은 보안을 고려하여 설계되었기 때문에 파일을 조작하는 등의 심각한 문제를 일으키기는 어렵겠지만 앞으로 보안기술을 회피하기 위한 다양한 시도를 할 것은 분명합니다.

네이티브 언어의 아키텍처와 플랫폼 의존성을 해결한 로딩

먼저 CPU 아키텍처 관점에서, 일반적으로 네이티브 프로그래밍 언어는 컴파일 시 CPU가 직접 분석할 수 있는 머신 코드(OPCODE) 명령어로 구성된 최종 프로그램 바이너리가 나옵니다. 머신코드는 컴파일시에 지정된 대상 아키텍처에서만 실행할 수 있으며, 다른 아키텍처 CPU는 해당 바이너리를 실행할 수 없습니다.

이를 해결하기 위해 OS에서 이기종 CPU 아키텍처를 위한 에뮬레이션 레이어를 추가하는 방법이 활발히 연구되고 있습니다. 최근 출시한 PC에 설치된 Windows 10 on ARM의 경우, 제한적으로 x86 프로그램 실행을 지원합니다.

이와 달리 아키텍처 의존성이 없는 WASM IR로 구성된 바이너리를 생성할 경우, 가상머신을 통해 실행 환경의 CPU의 아키텍처와 무관하게 실행할 수 있습니다. 이러한 특징을 표준 스팩에서는 ‘Hardware-Independent’로 표현합니다.

이 과정에서 가상머신의 방식은 인터프리팅 방식 혹은 JIT 컴파일링 방식, AOT 컴파일링 방식으로 구분할 수 있습니다. 대부분의 가상머신은 매 상황에 따라 최적화된 방법을 선택합니다. 자세한 이야기는 아래 최적화 기술에서 다시 언급합니다.

이어 바이너리 포맷 관점에서, 컴파일러는 명령어 집합 뿐만 아니라 OS별 정해진 파일 포맷에 따라 최종 프로그램 바이너리를 만듭니다. 우리가 익히 알고 있는 EXE, DLL은 마이크로소프트의 PE포맷이며, 유닉스/리눅스 계열에서 주로 사용하는 ELF포맷, 맥 계열에서 사용하는 Mach-O 포맷 등이 있습니다. OS는 기본적으로 다른 OS를 위한 파일 포맷을 지원하지 않습니다. 따라서 다른 OS에서 프로그램을 사용하기 위해서는 컴파일을 다시 해야 했습니다.

이와 달리 WASM은 특정 OS 등 플랫폼에 의존적이지 않은 표준화된 포맷으로, 가상머신만 준비되어 있다면 어느 환경에서나 실행할 수 있습니다. 이러한 특징을 표준 스팩에서는 ‘Platform-Independent’로 표현합니다. 이러한 특성 앞에 웹도 예외 대상은 아닙니다. 반드시 브라우저 환경을 필요하지 않으며, 독립적인 가상머신이나 기타 다른 환경에 통합되어 실행될 수 있습니다.

물론 WASM 가상머신은 기존의 OS/CPU 아키텍처별 방식으로 제공되어야 합니다.

위와 같은 ‘Hardware-Independent’ 및 ‘Platform-Independent’ 두 가지 특징은 가상머신 기반으로 설계된 프로그래밍 언어의 일반적인 특징입니다. 그러나 WASM은 특정 언어에 국한되지 않고 폭 넓게 적용할 수 있는 더 세분화된 IR을 설계함으로써 기존의 많은 언어도 WASM을 사용하여 높은 유연성을 가질 수 있는 길을 열어 주었습니다.

블록체인 노드는 아직까지는 서버 컴퓨팅에 일반적으로 쓰는 X86/AMD64 아키텍처가 대부분입니다. 그러므로 직접적인 블록체인에서 플랫폼의 활용 이점으로 내세우기에는 현 시점에서는 다소 억지스러울 수 있습니다. 다만 플랫폼의 발전에 따라 ARM-모바일 OS와 같은 다른 아키텍처를 사용하는 환경에서 WASM 컨트랙트를 실행할 경우가 생긴다면, 아무런 문제가 없습니다.

성능을 높이기 위한 가상머신의 최적화 기법

그렇다면 WASM IR 컴파일러와 인터프리터를 표준에 만족에 맞추어 구현한다면 CPU가 직접 실행하는 네이티브 바이너리에 가까운 성능을 낼 수 있을까요? WASM을 비롯 ECMA-262 등 언어 표준을 구현한 구글 v8 혹은 모질라의 SpiderMonkey와 같은 대중적인 엔진들은 언어에 대한 표준 외에도 많은 추가 요소들이 있습니다. 표준은 설계 원칙, 문법, 실행 결과에 대해서 정의할 뿐, IR의 생성 과정이나 실행 과정 등 소프트웨어 모델 설계에 대해서는 정의를 내리지 않습니다. 따라서 좋은 가상머신은 성능을 높이기 위해 많은 최적화 기법을 사용합니다.

더 작게, 더 빠르게 — IR 생성의 최적화

보통의 컴파일러는 더 작은 크기 혹은 더 빠른 속도로 실행할 수 있도록 코드를 생성하는 최적화 로직을 가지고 있습니다. 또한 두 최적화의 결과가 서로 배타적인 경우가 있습니다. 예를 들어 빠른 실행에 집중한다면 때로는 더 많은 코드 크기로 구성됩니다. 혹은 더 작은 코드 크기를 추구한다면 때로는 실행이 비교적 느릴 수 있습니다.

최적화에 관련된 매개변수는 매우 다양합니다. 실행 대상 CPU 아키텍처가 가지고 있는 범용 레지스터는 몇개인지, 하드웨어 가속과 같은 최신 기능을 지원하는지, 함수 호출 규약은 무엇인지 등 매우 다양하며, 최신 기법도 하드웨어에서부터 소프트웨어까지 모든 영역을 통틀어 꾸준히 연구되고 있습니다.

WASM 컴파일러는 IR을 생성하기 위해 어떤 최적화 방법에 집중해야 할까요? 잠시 생각해보면 앞서 다룬 Platform Independent와 Hardware Independent 두 가지 유연성을 고려한다면 쉽게 결정하기 어렵습니다. 컴파일러가 웹 브라우징 환경에서 실행될 것이라 미리 판단하여 컴퓨팅 파워 소모가 적도록 최적화를 한다면 그 외 환경에서는 불리해 질 수 있습니다. 실행 속도를 높이기 위해 코드 크기가 커지는 문제를 무시하고 최적화를 진행한다면, 네트워크 상태가 열악한 환경에서는 코드를 전송하는데 더 많은 시간을 사용해야 하기 때문에 실행 속도가 빠르더라도 결과적으로 더 느린 결과를 얻을 수도 있습니다.

인터프리팅의 한계와 런타임 최적화의 백미 — JIT 컴파일 & AOT 컴파일

인터프리터의 기초적인 구현 수준은 AST(Abstract-Syntax-Tree)를 순환하거나, IR을 슬라이딩하며 분석후 실행합니다. 이 과정은 CPU OPCODE를 직접 실행하는 네이티브 바이너리와 비교했을 때 매우 느릴 수 밖에 없습니다.

최적화가 잘 된 인터프리터 엔진은 IR 블럭을 최초 실행하는 시점에 한번 CPU OPCODE로 변환하는 과정을 거칩니다. 이를 JIT(Just-In-Time) 컴파일이라 하며, 자주 실행되는 부분에 적용했을 때 효율적입니다. 다만, 컴파일을 위한 시간과 리소스가 필요하므로 한두번만 실행되는 간단한 코드 단위의 모음에 적용될 경우 단순한 인터프리팅보다 느려질 수도 있습니다. 만약 프로그램을 받은 후 실행을 시작하기 전에 충분한 시간을 확보할 수 있는 경우 실행 전에 전체 IR을 변환하는 AOT(Ahead-Of-Time)컴파일이 적합할 수도 있습니다.

WASM은 위와 같이 잘 알려진 최적화 과정에 대해 미리 고려하여 설계되었습니다.

실행 대상과 목적을 고려해 최적화 방법을 선택합니다. 의존성 제거를 위해 대상에 대한 정보를 미리 알 필요가 없어야 한다는 설계 원칙이 기존의 실행 환경과 목적을 미리 알아야 하는 최적화 선택 방법과 동시에 만족되기 어렵습니다. 결국 자동으로 최적의 경우를 선택하는데에는 많은 걸림돌이 있습니다. 결국 WASM 모듈 개발자가 여러 환경을 고려해 가장 적절한 최적화 방법을 선택해야 합니다. 또한 WASM 가상머신의 개발 입장에서는 기존 네이티브 컴파일의 최적화 방법들을 그대로 적용하기 보다, WASM에 알맞는 최적화 방법들을 개발해야 합니다.

블록체인에서 WASM은 그저 빛과 소금일까?

블록체인과 WASM, 두가지 새로운 기술이 나타난 시기가 비슷하고 또한 거의 동시에 집중을 받은 것은 어쩌면 놀라운 우연일지도 모릅니다. 조금은 매니악했던 컴파일러와 가상머신 분야가 다시 조명을 받는 것은 개인적으로 매우 기쁜 일이기도 합니다.

블록체인 플랫폼의 실행 환경은 기존의 일반적인 실행 환경과 비교하면 굉장히 다릅니다.익명의 개발자가 작성한 코드를 검증없이 서버가 실행한다는 것은 탈중앙화 플랫폼의 trustless를 위해서는 꼭 필요한 일이지만 노드의 보안을 고려한다면 매우 위험한 일이기도 합니다. 그래서 WASM의 샌드박스 환경을 비롯한 보안 설계 원칙은 탈중앙화 플랫폼에서 더욱 중요합니다.

웹컴퓨팅 용도로 고려된 WASM인 만큼, 브라우저 엔진의 경험을 그대로 녹일 수 있었다면 얼마나 좋았을까요. 블록체인 플랫폼에 적용하기 위해서는 몇 가지 문제를 해결해야 합니다.

블록체인을 위한 고려할 점, 발전의 발목을 잡다

트랜잭션 비용

일부 블록체인 플랫폼은 스패밍(spamming)과 서비스 거부 공격(Denial-of-Service attack)을 방지하기 위해 IR 당 비용을 부과합니다. 각 명령어에 따라 비용이 조금씩 다르지만 일반적으로는 실행할 명령어가 많아질수록 더 많은 비용이 들거라 예상할 수 있습니다.

위에서 다룬 최적화 방법을 다시 생각해 보면, 실행 속도 최적화를 위해 때로는 코드 크기가 커지는 것을 감수하기도 합니다. 스마트 컨트랙트를 실행하는 입장에서는 당연히 트랜잭션 비용을 줄이고 싶겠지만, 스마트 컨트랙트를 이용해 서비스를 운영해야하는 입장이나 노드 운영의 입장에서는 실행 속도를 무시할 수 없습니다.

비결정론적 결과

동일한 입력이라면, 일관되게 동일한 출력을 가지는 경우를 (Deterministic)이라고 합니다. 이번에는 반대 경우인 비결정론적(Non Deterministic)인 상황에 대해 이야기하려 합니다.

동일한 입력과 시점으로 컨트랙트를 실행할 때 모든 분산 노드가 같은 실행 결과를 가져야 합니다. 반드시 이 조건이 성립해야만 최종 합의에 이를 수 있습니다. 그래서 스마트 컨태랙트에 비결정론적 경우가 있어서는 안됩니다.

이해하기 쉬운 상황 중 하나는 비결정론적 상황을 고려하지 않은 일반적인 난수(random number) 생성 함수를 스마트 컨트랙트에서 사용하는 경우입니다. 또한 WASM의 표준 정의에서도 비결정론적 상황이 있습니다[3]. 스레드 기능이 그 중 하나인데, 멀티스레드로 특정 메모리를 동시 제어하면 나타날 수 있습니다.

결국 스마트 컨트랙트 WASM 모듈에 대해 결정론적 경우를 제한할 수 있고, 검사할 수 있어야 합니다. 그런데 사실, 굉장히 어려운 주제입니다. 알려진 몇 가지 경우에 대해서는 비표준 컴파일러 옵션이나 강제로 실행 결과를 제한하는 등 표준과 다른 방법을 채택해 해결을 모색합니다. 하지만 스마트 컨트랙트 WASM 모듈을 생성할 때 이를 준수했는지, 모든 비결정론적 위협이 사라졌는지 받아들이는 입장에서 보증하기는 어렵습니다. IR코드에 대한 정적분석과 에뮬레이션 등 여러 방법들을 새로운 컨트랙트가 등록될 때 검사해야하며[4], 이 결과를 최대한 신뢰하는 수 밖에 없습니다.

제 3자가 작성한 코드 실행에 숨겨진 다양한 취약점 위협의 공포

앞에서 잠시 이야기한 것처럼 서버에서 제 3자가 작성한 코드를 실행하는 것은 굉장히 도전적인 방법입니다. 가상머신 자체의 취약점이 있을 경우, 실행 결과를 보장할 수 없거나 극단적으로는 서버의 제어 권한이 탈취될 수도 있기 때문입니다.

웹 컴퓨팅을 예를 들어, 브라우저 엔진의 취약점을 공략하는 악성 웹 사이트를 접속하는 경우 실행하는 클라이언트에 실제 공격으로 가해질 수 있습니다. 블록체인 플랫폼의 경우 클라이언트 대신 서버를 위협할 수 있습니다. 그리고 이 우려는 실제로 다른 블록체인 프로젝트에서 가상머신을 대상으로 하는 취약점 사례가 나왔습니다[5]. 다행히 실제 피해 사례가 발생하기 앞서, 보안 연구팀의 발견으로 패치까지 해결되었습니다[6].

WASM은 긍정적인 면 뿐만 아니라, 취약점 연구에서도 새로운 입력 경로로 떠오릅니다[7][8]. 새로운 최적화 기능 구현이 늘어날 수록 점검하기 이전인 취약점 위협 요소가 늘어나는 만큼 조심을 다해야 합니다.

보수적인 검증과 진보적인 최적화 사이

앞에서는 WASM 표준과 최적화를 위한 다양한 방법들, 그리고 브라우저의 WASM 실행엔진과 같은 구현체를 블록체인 플랫폼에 그대로 사용하기 어려운 이유에 대해 설명드렸습니다.

장기적으로 웹에서의 WASM 가상머신 발전과 블록체인에서의 WASM 가상머신 발전의 차이는 벌어질 수 밖에 없습니다. 가상머신과 컴파일러는 결코 소수의 인원으로 한정된 기간 내 완성하고 마무리를 지을 수 있는 분야는 아닙니다. 개발 비용이 결코 작지 않음을 감안해야 합니다.

또한 충분한 인력과 시간이 마련되더라도, 어떤 개발 방향을 선택해야할 지는 여전히 어렵습니다. 보수적으로 최대한 까다로운 검증을 거쳐가야 할까요? 아니면 위협을 무릅쓰고 상용화에 적합하도록 진취적으로 새로운 시도를 이어가야 할까요? 혹은 어떻게든 VM 개발에 충분한 경험이 있는 브라우저 엔진을 사용할 방법을 만들어 볼 수는 없을까요?

당연히 정답은 없기에 프로젝트 주도권의 성향마다 다른 전략을 실행합니다. 보수적으로 검증을 앞세우는 경우,여러 최적화 방법을 도입하지 않고,기초적인 방식으로 구현을 한정합니다. 반면 위협을 감안하고 최적화를 시도하는 경우, llvm 백엔드를 두고 IR을 최적화하며 JIT을 도입해 런타임에 머신코드를 생성하기도 합니다.

다만 아직까지 브라우저 엔진을 직접 이용하려는 프로젝트를 보지는 못했습니다. 브라우저 엔진 개발 회사에서 블록체인 플랫폼을 시도한다면 경우가 생길 수 있을까요?

마치며

블록체인 플랫폼의 WASM 가상머신은 고민할 부분이 많습니다. 독자적인 기술 방향을 추구하는 많은 플랫폼들이 있습니다. 목적, 개발진의 특기 등을 고려했을 때 서로 다른 창의적인 기술들이 나올 수 있음에 전체의 점진적인 발전에 도움이 됩니다. 하지만 때로는 독자적인 기술을 추구하기에는 현실적으로 기술 인력이 부족하거나, 어느 길로 가더라도 대부분이 가진 공통점으로 빠질 수도 있습니다. 굳이 무리하면서까지 독자적으로 개발할 필요가 없을 수도 있다는 이야기죠. 결론을 단정짓기는 어렵습니다.

이에 어떻게 하면 개발 공수가 드는 블록체인만의 특수화 요소를 줄여가며, 경쟁 등 복잡한 이해관계를 해소하고, 각자의 노력을 무의미하지 않게 만들어 하나의 공통점을 만들어갈 수 있을까요?

우리는 최적화 방법에 대한 트렌드 등 논의를 꾸준히 나누면서도, 까다로운 검토를 놓치지 않겠습니다. 막연한 두려움에 의한 제자리 걸음도 경계해야 하지만 막연한 도전으로 안정성을 해치는 일을 막기 위해 끊임없이 살펴보아야 합니다. Execution Engine에서 합의 진행 중인 블록의 트랜젝션을 사전에 AOT 컴파일해두는 최적화를 적용하거나, 컨트랙트의 취약점을 분석할 수 있는 툴과 같이, 최적화와 검증 두가지 측면의 연구를 진행할 계획입니다. 또한 에이치닥 플랫폼에 한계를 가지지 않고, 탈중앙화 생태계에서 모두가 유용하게 사용할 수 있도록 범용적인 방향으로 나아가려 합니다.

앞으로도 기술 집약적으로 꾸준히 발전하는 모습을 보여드리도록 하겠습니다.

References

--

--