저자: Wuyue, 괴짜 web3
우리 모두 알고 있듯이 EVM은 이더리움의 "실행 엔진"이자 "스마트 계약 실행 환경"으로 자리잡고 있으며 이더리움의 가장 중요한 핵심 구성 요소 중 하나라고 할 수 있습니다. 퍼블릭 체인은 수천 개의 노드를 포함하는 개방형 네트워크입니다. 서로 다른 노드의 하드웨어 매개변수는 크게 다릅니다. 스마트 계약이 여러 노드에서 동일한 결과를 실행하고 "일관성"을 충족하도록 하려면 서로 다른 장치에서 실행해야 합니다. 모든 플랫폼에 동일한 환경이 구축되어 있으며 가상 머신이 이러한 효과를 얻을 수 있습니다.
Ethereum의 가상 머신 EVM은 다양한 운영 체제(예: Windows, Linux, macOS) 및 장치에서 동일한 방식으로 스마트 계약을 실행할 수 있습니다. 이러한 교차 플랫폼 호환성을 통해 각 노드는 계약 결과를 실행한 후 일관된 결과를 얻을 수 있습니다. 가장 일반적인 예는 Java 가상 머신 JVM입니다.
블록 탐색기에서 일반적으로 볼 수 있는 스마트 계약은 먼저 EVM 바이트 코드로 컴파일된 다음 체인에 저장됩니다. EVM은 계약을 실행할 때 이러한 바이트코드를 순서대로 직접 읽습니다. 바이트코드에 해당하는 각 명령(opCode)에는 해당 가스 비용이 있습니다. EVM은 실행 중 각 명령의 가스 소비를 추적하며 소비는 작업의 복잡성에 따라 달라집니다.
또한 EVM은 Ethereum의 핵심 실행 엔진으로서 직렬 실행을 사용하여 트랜잭션을 처리합니다. 모든 트랜잭션은 단일 대기열에 대기하고 정해진 순서에 따라 실행됩니다. 병렬화를 사용하지 않는 이유는 블록체인이 일관성을 엄격하게 충족해야 하기 때문입니다. 트랜잭션 처리는 모든 노드에서 동일한 순서로 처리되어야 하기 때문에 병렬화를 도입하지 않으면 트랜잭션 순서를 정확하게 예측하기가 어렵습니다. 대응하는 스케줄링 알고리즘이지만 이는 더 복잡합니다.
2014년부터 2015년까지 이더리움 창립팀은 시간적 제약으로 인해 직렬실행 방식을 선택했습니다. 간단하고 유지 관리가 쉽도록 설계되었기 때문입니다. 그러나 블록체인 기술의 반복과 사용자 기반의 증가로 인해 블록체인은 TPS 및 처리량에 대한 요구 사항이 점점 더 높아졌습니다. 롤업 기술의 출현 및 성숙한 구현 이후 EVM 직렬 실행으로 인한 성능 병목 현상이 의심할 여지 없이 노출되었습니다. 이더리움의 두 번째 레이어.
Layer 2의 핵심 구성 요소인 Sequencer는 단일 서버 형태로 모든 컴퓨팅 작업을 수행합니다. Sequencer와 협력하는 외부 모듈의 효율성이 충분히 높으면 최종 병목 현상은 Sequencer 자체의 효율성에 따라 달라집니다. 시간이 지나면 연속 실행이 큰 장애물이 될 것입니다.
opBNB 팀은 DA 계층과 데이터 읽기 및 쓰기 모듈을 극도로 최적화하여 초당 최대 2,000개 이상의 ERC-20 전송을 수행할 수 있습니다. 이 숫자는 높은 것 같지만, 처리해야 할 트랜잭션이 ERC-20 전송보다 훨씬 복잡하다면 TPS 값은 크게 줄어들 수밖에 없습니다. 따라서 트랜잭션 처리의 병렬화는 앞으로 피할 수 없는 추세가 될 것입니다.
다음으로, 기존 EVM의 한계와 병렬 EVM의 장점을 설명하기 위해 보다 구체적인 세부 사항부터 시작하겠습니다.
Ethereum 트랜잭션 실행의 두 가지 핵심 구성 요소
코드 모듈 레벨에서는 EVM 외에 go-ethereum의 트랜잭션 실행과 관련된 또 다른 핵심 구성 요소는 이더리움에서 계정 상태 및 데이터 저장을 관리하는 데 사용되는 stateDB입니다. 이더리움은 데이터베이스 인덱스(디렉토리) 역할을 하기 위해 Merkle Patricia Trie라는 트리 구조를 사용합니다. EVM의 각 트랜잭션 실행은 stateDB에 저장된 특정 데이터를 변경하며 이러한 변경 사항은 결국 Merkle Patricia Trie(이하 전역 상태 트리).
구체적으로, stateDB는 EOA 계정과 컨트랙트 계정을 포함한 모든 이더리움 계정의 상태를 유지하는 역할을 담당합니다. 여기에 저장되는 데이터에는 계정 잔액, 스마트 컨트랙트 코드 등이 포함됩니다. 트랜잭션 실행 과정에서 stateDB는 해당 계정의 데이터를 읽고 씁니다. 트랜잭션이 실행된 후 stateDB는 지속성을 위해 기본 데이터베이스(예: LevelDB)에 새 상태를 제출해야 합니다.
일반적으로 EVM은 스마트 계약 명령을 해석 및 실행하고 계산 결과에 따라 블록체인의 상태를 변경하는 역할을 담당하는 반면, StateDB는 글로벌 상태 저장소 역할을 하며 모든 계정 및 계약의 상태 변경을 관리합니다. 두 사람은 이더리움의 트랜잭션 실행 환경을 구축하기 위해 협력합니다.
직렬 실행의 구체적인 프로세스
이더리움에는 EOA 전송과 계약 거래라는 두 가지 유형의 거래가 있습니다. EOA 이체는 가장 간단한 거래 유형으로 일반 계좌 간 ETH 이체입니다. 이러한 종류의 거래에는 계약 호출이 포함되지 않으며 매우 빠르게 처리됩니다. 운영의 단순성으로 인해 EOA 전송에 부과되는 가스 요금은 매우 낮습니다.
단순한 EOA 전송과 달리 계약 거래에는 스마트 계약의 호출 및 실행이 포함됩니다. EVM은 계약 트랜잭션을 처리할 때 스마트 계약의 바이트코드 명령을 하나씩 해석하고 실행해야 합니다. 계약의 논리가 복잡할수록 더 많은 명령이 필요하고 더 많은 리소스를 소비합니다.
예를 들어, ERC-20 전송 처리 시간은 EOA 전송 처리 시간의 약 2배인 반면, Uniswap의 트랜잭션 작업과 같은 보다 복잡한 스마트 계약의 경우 EOA 전송보다 시간이 더 오래 걸리고 심지어 10배 이상 느릴 수도 있습니다. DeFi 프로토콜은 거래 중 유동성 풀, 가격 계산, 토큰 스왑 등 복잡한 로직을 처리해야 하고 매우 복잡한 계산을 요구하기 때문입니다.
그렇다면 EVM과 stateDB 두 구성요소가 직렬 실행 모드에서 트랜잭션을 처리하기 위해 어떻게 협력합니까?
이더리움 설계에서 블록 내의 트랜잭션은 순서대로 하나씩 처리되며 각 트랜잭션(tx)은 트랜잭션의 특정 작업을 수행하는 데 사용되는 독립적인 인스턴스를 갖습니다. 각 트랜잭션은 서로 다른 EVM 인스턴스를 사용하지만 모든 트랜잭션은 동일한 상태 데이터베이스인 stateDB를 공유합니다.
트랜잭션 실행 중에 EVM은 stateDB와 지속적으로 상호 작용하고, stateDB에서 관련 데이터를 읽고, 변경된 데이터를 다시 stateDB에 써야 합니다.
코드 관점에서 EVM과 stateDB가 협력하여 트랜잭션을 실행하는 방법을 대략적으로 살펴보겠습니다.
1. processBlock() 함수는 Process() 함수를 호출하여 블록에 포함된 트랜잭션을 처리합니다.
2. Process() 함수에 for 루프가 정의되어 있으며 트랜잭션이 하나씩 실행되는 것을 볼 수 있습니다.
3. 모든 트랜잭션이 처리된 후 processBlock() 함수는 writeBlockWithState() 함수를 호출한 다음,stateb.Commit() 함수를 호출하여 상태 변경 결과를 제출합니다.
블록 내 모든 트랜잭션이 실행되면 stateDB의 데이터는 앞서 언급한 전역 상태 트리(Merkle Patricia Trie)에 Commit되고 새로운 상태 루트(stateRoot)가 생성됩니다. 상태 루트는 각 블록의 중요한 매개변수로, 블록이 실행된 후 새로운 전역 상태의 "압축 결과"를 기록합니다.
EVM의 직렬 실행 모드의 병목 현상이 명백하다는 것을 이해하는 것은 어렵지 않습니다. 트랜잭션은 순서대로 실행을 위해 대기열에 있어야 하며 시간이 오래 걸리는 스마트 계약 트랜잭션이 있는 경우 다른 트랜잭션은 처리되기 전에만 기다릴 수 있습니다. 분명히 CPU 및 기타 하드웨어 리소스를 완전히 활용할 수 없으므로 효율성이 크게 제한됩니다.
EVM용 멀티스레드 병렬 최적화 솔루션
실제 사례를 사용하여 직렬 실행과 병렬 실행을 비교하면 전자는 카운터가 하나만 있는 은행에 비유되고, 병렬 EVM은 카운터가 여러 개 있는 은행에 비유됩니다. 병렬 모드에서는 여러 스레드를 시작하여 동시에 여러 트랜잭션을 처리할 수 있으며 효율성을 여러 번 향상시킬 수 있지만 까다로운 부분은 상태 충돌 문제입니다.
여러 거래가 한 계정의 데이터를 다시 쓰겠다고 선언하면 동시에 처리될 때 충돌이 발생합니다. 예를 들어 하나의 NFT만 발행할 수 있으며 거래 1과 거래 2 모두 발행을 원한다고 선언합니다. 모든 요청이 이행되면 분명히 오류가 발생하며 이러한 상황을 처리하려면 조정이 필요합니다. 실제 작업에서 상태 충돌은 위에서 언급한 것보다 더 자주 발생하는 경우가 많기 때문에 트랜잭션 처리를 병렬화하려면 상태 충돌을 처리할 수 있는 대책이 필요합니다.
EVM을 위한 Reddio의 병렬 최적화 원칙
ZKRolup 프로젝트 Reddio의 EVM에 대한 병렬 최적화 아이디어를 살펴볼 수 있습니다. Reddio의 아이디어는 각 스레드에 트랜잭션을 할당하고 각 스레드에 보류 상태 DB라는 임시 상태 데이터베이스를 제공하는 것입니다. 구체적인 내용은 다음과 같습니다.
1. 다중 스레드 병렬 트랜잭션 실행: Reddio는 스레드 간 간섭 없이 동시에 여러 트랜잭션을 처리하도록 여러 스레드를 설정합니다. 이로 인해 트랜잭션 처리 속도가 여러 번 빨라질 수 있습니다.
2. 각 스레드에 임시 상태 데이터베이스 할당: Reddio는 각 스레드에 독립적인 임시 상태 데이터베이스(pending-stateDB)를 할당합니다. 각 스레드는 트랜잭션을 실행할 때 전역 stateDB를 직접 수정하지 않고 일시적으로 상태 변경 결과를 보류 상태DB에 기록합니다.
3. 동기식 상태 변경: 블록 내 모든 트랜잭션이 실행된 후 EVM은 각 보류 상태DB에 기록된 상태 변경 결과를 글로벌 상태DB에 차례로 동기화합니다. 서로 다른 트랜잭션을 실행하는 동안 상태 충돌이 없으면 보류 중 상태DB의 레코드가 전역 상태 DB에 성공적으로 병합될 수 있습니다.
Reddio는 트랜잭션이 상태 데이터에 올바르게 액세스하고 충돌을 피할 수 있도록 읽기 및 쓰기 작업을 처리하는 방법을 최적화했습니다.
·읽기 작업: 트랜잭션이 상태를 읽어야 할 때 EVM은 먼저 보류 상태의 ReadSet을 확인합니다. ReadSet에 필요한 데이터가 존재한다고 표시되면 EVM은 보류 상태DB에서 직접 데이터를 읽습니다. 해당 키-값(키-값 쌍)이 ReadSet에 없으면 이전 블록에 해당하는 전역 stateDB에서 기록 상태 데이터를 읽습니다.
·쓰기 작업: 모든 쓰기 작업(즉, 상태 수정)은 전역 stateDB에 직접 기록되지 않고 먼저 Pending-state의 WriteSet에 기록됩니다. 트랜잭션 실행이 완료된 후 충돌 감지를 통해 상태 변경 결과를 전역 StateDB에 병합해 봅니다.
병렬 실행의 주요 문제는 상태 충돌입니다. 이는 여러 트랜잭션이 동일한 계정의 상태를 읽고 쓰려고 할 때 특히 중요합니다. 이러한 이유로 Reddio는 충돌 감지 메커니즘을 도입합니다.
· 충돌 감지: 트랜잭션 실행 중에 EVM은 다양한 트랜잭션의 ReadSet 및 WriteSet를 모니터링합니다. 동일한 상태 항목을 읽거나 쓰려고 하는 여러 트랜잭션이 발견되면 충돌로 간주됩니다.
· 충돌 처리: 충돌이 감지되면 충돌하는 트랜잭션은 재실행이 필요한 것으로 표시됩니다.
모든 트랜잭션이 실행된 후 여러 보류 상태 DB의 변경 기록이 전역 상태 DB로 병합됩니다. 병합이 성공하면 EVM은 최종 상태를 전역 상태 트리에 커밋하고 새로운 상태 루트를 생성합니다.
다중 스레드 병렬 최적화의 성능 향상은 특히 복잡한 스마트 계약 트랜잭션을 처리할 때 분명합니다.
병렬 EVM에 대한 연구에 따르면 충돌이 적은 워크로드(트랜잭션 풀에서 충돌이 적은 트랜잭션 또는 동일한 리소스를 차지하는 트랜잭션)에서 벤치마크 테스트의 TPS가 기존 직렬 실행에 비해 약 3~5배 향상되었습니다. 충돌이 심한 워크로드에서는 이론적으로 모든 최적화 방법을 사용하면 60배까지 도달할 수 있습니다.
요약
Reddio의 EVM 다중 스레드 병렬 최적화 솔루션은 각 트랜잭션에 임시 상태 라이브러리를 할당하고 다른 스레드에서 병렬로 트랜잭션을 실행함으로써 EVM의 트랜잭션 처리 기능을 크게 향상시킵니다. 읽기 및 쓰기 작업을 최적화하고 충돌 감지 메커니즘을 도입함으로써 EVM 기반 퍼블릭 체인은 상태 일관성을 보장하면서 트랜잭션의 대규모 병렬화를 달성할 수 있으며 기존 직렬 실행 모드로 인해 발생하는 성능 병목 현상을 해결할 수 있습니다. 이는 Ethereum Rollup의 향후 개발을 위한 중요한 기반을 마련합니다.
후속에서는 스토리지 효율성을 최적화하여 효율성을 더욱 향상시키는 방법, 충돌이 높을 때의 최적화 솔루션, 최적화를 위해 GPU를 사용하는 방법 등 Reddio의 구현 세부 사항을 추가로 분석할 것입니다.