이더리움 노드 RPC 서비스도 통일이 필요할 때

Luke Park
취미로 논문 읽는 그룹

--

Kim, Shinhae, and Sungjae Hwang. “EtherDiffer: Differential Testing on RPC Services of Ethereum Nodes.” Proceedings of the 31st ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering. 2023.

이더리움 블록체인을 필두로 많은 탈중앙화 어플리케이션(DApp)들이 등장했는데, 통상 DApp들은 블록체인상에서 데이터를 저장하고 연산하는 on-chain단 로직과 프론트엔드로 대표되는 off-chain단 로직이 있다. 그리고 이들간의 상호작용(주로 off-chain에서 on-chain으로의 요청)이 당연히 필요한데, 크게 체인 데이터가 필요한 요청(call)과 체인의 상태를 변화시키는 요청(send)가 있다. 그러한 call과 send를 처리하기 위해 이더리움 노드들은 공식 RPC 스펙을 구현하고 프론트엔드에 이들을 열어둔다.

그러나, 이 스펙이 두 가지 이유로 불충분한데: (1) 비결정론적 이벤트 핸들링에 있어 상세가 부족하고, (2) invalid한 아규먼트에 대한 스펙—오류처리관련—이 부족하다.

예를 들어, “없는 블록해시에 대한 eth_getLogs 요청”에 대한 처리는 어떻게 될 것인가? 어떤 클라이언트들은 이를 -32000 에러코드와 “unknown block” 메시지를 리턴하는 것으로 처리하지만, 다른 클라이언트에서는 빈 배열을 반환하는 것으로 처리하고 있다. 더 중요한 것은, 이 요청 예시가 예시를 위한 예시를 들기 위해 억지로 만든 상황이 아니라는 것이다. 리오그(Reorg) 상황을 통해 충분히 일어날 수 있는 일(비결정론적)이다.

본 논문에서는 이를 효과적으로 분석하기 위해 최초로 syntax-aware 그리고 semantics-aware한 차등 테스팅을 진행했다. EtherDiffer라 이름붙은 이 방법론 및 구현체는 실제로 (총 전체 이더리움 클라이언트 점유율의 99.7%를 차지하는) 4개의 서로다른 클라이언트를 자동으로 분석할 수 있다.

  • Geth v1.10.21-stable
  • Erigon 2022.07.04-alpha
  • Nethermind v1.13.6
  • Hyperledger Besu v22.4.4

EtherDiffer는 두 페이즈로 구성된다. 첫 번째 생성 페이즈에서는 체인을 성장시키는 것을 목적으로 보조 노드들이 작동된다. 로컬 네트워크를 구축하는 것인데, 각각 다른 종류의 클라이언트 4개 노드로 구성한다. 의도적으로 비결정론적 상태를 만들기 위해 블록 전파에 딜레이를 섞어, 노드가 엉클헤더를 가질 수 있도록 한다.

체인이 설정된 높이에 도달하면 작업이 중지되고, 모든 노드들이 동기화를 마친 후, 두 번째 페이즈인 테스팅 페이즈에 돌입한다. EtherDiffer는 메서드를 선택하고, Generator를 통해 의미론적으로 유효한 템플릿 코드를 생성하고, Mutator를 통해 유형은 유지하되 인자 중 하나를 의미론적으로 유효하지 않은 값으로 확률적으로 변경한다. 마지막으로 Test Case Converter를 통해 이를 대상 노드에 맞는 테스트 케이스로 생성한다. 각 노드 테스트 케이스를 수행하면 Error Checker를 통해 값을 반환하는 노드가 존재하고 다른 노드들에서는 오류가 발생하는지 여부를 체크하고, Value Checker를 통해 값이 서로 일치하는지를 검사한다.

EtherDiffer는 의미적으로 유효한 테스트 케이스와 유효하지 않지만 실행 가능한 테스트 케이스를 각각 98.8%와 95.4%의 성공률로 자동 생성할 수 있었다. 이를 통해 크래시 및 서비스 거부 버그(DoS)와 같은 11개의 구현 버그를 포함하여 48개의 문제를 탐지했고, 이 중 44개를 제보했다.

보다 구체적으로, 메소드 아규먼트들의 구문적 의미론적 요구사항을 담을 수 있는 domain-specific language (DSL)을 정의한다.

예를 들어, 그림 (a)의 getTransactionFromBlock이라는 메소드를 살펴보자. 이는 두 개의 인자를 가지는데, 첫 번째는 블록넘버 또는 블록해시 또는 latest를 인자로 받고, 두 번째는 가져올 트랜잭션의 인덱스를 인자로 받는다.

가져올 트랜잭션의 인덱스를 살펴보기 위해 getBlock 메소드의 호출이 또 필요하게 되고, 이는 블록넘버나 블럭해시를 인자로 수행된다. 이러한 동적인 요소는 (c) 매핑테이블에서 우선 저장해둔 후, (a)에서 인자로 넘겨준 값(@_)을 이용해 getBlock을 호출한다. 가령, (b)에서의 예시처럼 “latest”를 인자로 가진다면 getBlock(“latest”, false).transactions.index()를 호출한다.

이후 Mutator를 통해서 코드를 변경하는데, 가령 (d)에서처럼 Mutator가 p_Range(1, 1024)를 통해 랜덤 숫자를 선출하고 원래의 인덱스를 대체한다.

마지막으로, 이에 기반해, 이더리움 공식 라이브러리 web3.js를 활용해 테스트 케이스 생성을 수행한다. 서로 다른 클라이언트에 맞는 서로 다른 4개의 테스트케이스 코드가 생성된다.

저자들은 의미론적으로 유효한 테스트 케이스를 생성할 수 있는 EtherDiffer의 효율성을 평가하기 위해 3,000개의 유효한 테스트 케이스를 생성하고 오류 없이 성공적으로 실행을 완료했는지 여부를 확인했다. (a)를 보면 총 3,000개 중 2,965개의 유효한 테스트 케이스가 실행 가능했다.

35개의 실패한 테스트 사례를 수동으로 조사한 결과, 각 인자가 의미론적으로 유효함에도 불구하고 대부분의 경우 결합 시 오류가 발생했다고 한다. 예를 들어 getPastLogs 메소드에서 EtherDiffer는 각 블록 번호에 대해 유효한 인수를 생성하는 데 성공했지만, 시작 번호가 끝 번호보다 커서 에러가 발생한 것이다.

또한, EtherDiffer는 돌연변이 전략을 적용하여 의미적으로 유효한 테스트 케이스를 유효하지 않은 것으로 변환한다. 그 효과를 평가하기 위해 저자들은 3,000개의 유효한 테스트 케이스에 대해 돌연변이를 생성하고 유효하지 않은 테스트 케이스가 라이브러리 오류를 트리거하지 않는지, 노드 생성 오류를 포함한 결과를 반환하는지를 확인했다. 그 결과, (b)를 보면, 2,863개의 유효하지 않은 테스트 케이스가 라이브러리 오류를 생성하지 않았다.

실패한 137개의 테스트 사례를 수동으로 조사한 결과, 대부분이 라이브러리가 트랜잭션 실패를 줄이기 위해 트랜잭션 객체 인자에 대한 추가 검사를 수행하기 때문에 실패한 것으로 확인되었다. 예를 들어, 가스 값이 Mutator를 통해 변경되어 필요한 최소 가스보다 작아서 발생했다.

이더리움 노드 구현 간의 차이를 감지하기 위해 EtherDiffer를 10회 반복한 결과, 위 테이블에서처럼 48개 클래스의 차이가 발견되었다. 각 반복마다 300 높이의 비결정적 체인과 600개의 테스트 케이스를 사용하였다. 즉, 총 24,000(=10*600*4)개의 테스트 사례로 구성된 6,000(=10*600)개의 테스트 사례를 기반으로 차등 테스트를 수행했다.

실험 결과, 총 1,536개의 오류 불일치와 1,693개의 값 불일치가 발견되었다.

물론 이러한 불일치는 블록체인, 즉 on-chain state와 무관하게 off-chain상에서 발생하는 것이므로 그다지 심각한 사안은 아니라 할 수 있다. 결국 중요한 것은 on-chain state이기 때문이다. DApp 개발자 입장에서도 자신이 사용한/할 노드의 스펙에만 맞추면 되기 때문에 크게 불편하지 않았던 문제일 수 있다.

그러나 확실한 것은 클라이언트마다 서로 다르게 응답하는 또는 크래시를 만드는 (원 논문 참조) 요청이 있다는 점이며, 이러한 노드 간의 불일치를 제거하면 더욱 쉽고 안정적인 탈중앙화 어플리케이션 생태계를 구축할 수 있을 것이라는 점이다. 실제로 많은 개발자들이 (특히 노드 개발자들이) 본 연구에 대한 감사 인사를 남겼다:

“thank you for these reports, this is very helpful”,
“would be very happy to have you contribute your tests”,
“super interested in this project · · · like to read more”

또한 저자들은 기꺼이 실험에 사용한 코드와 데이터셋을 공개하였다. 관심있는 연구자들은 참고하길 바란다.

--

--