Skip to content

Engineering · XODE omni

폰이 노드라고 말하려면, 무엇을 증명해야 하는가 ​

스마트폰에서 블록체인 라이트노드를 돌리는 건 어렵지만 풀 수 있는 문제다. 진짜 어려운 건 그다음이다 — 거기에 보상을 붙이는 순간, 돌리지 않고 돌렸다고 말하는 게 훨씬 싸게 먹히기 시작한다.

"폰이 노드"라는 말의 정확한 뜻 ​

모바일 DePIN 앱에서 "당신의 폰이 노드가 됩니다"라는 문구는 흔하다. 대부분의 경우 그 말이 실제로 뜻하는 건 서버에 주기적으로 신호를 보낸다이다. 앱이 백엔드에 "저 살아있어요"를 5분마다 찍고, 서버가 그걸 세어 보상을 준다.

그건 노드가 아니다. 그건 출석부다.

omni에서 우리는 이 구분을 코드 수준에서 나눠 두었다. 앱 내부에는 상태 플래그가 두 개 있다.

약한 주장

체인에서 직접 받음

백엔드를 거치지 않고 체인 RPC에서 최신 블록을 가져왔다. 중개자는 줄었지만, 여전히 누군가 알려준 숫자다.

강한 주장

폰이 스스로 검증함

기기 안의 라이트클라이언트가 파이널리티를 직접 확인했다. 물어본 게 아니라 계산한 숫자다.

두 번째 플래그는 네이티브 smoldot 라이트클라이언트가 warp sync를 마치기 전까지 0으로 남아 있다. 검증되지 않았으면 검증됐다고 말하지 않는다. 이 구분이 앱 전체 설계의 출발점이다.

보상을 붙이는 순간 문제가 바뀐다 ​

노드를 돌리면 토큰을 준다고 해보자. 그 순간 가장 합리적인 행동은 노드를 돌리는 게 아니라, 노드를 돌린 것처럼 보이는 가장 싼 방법을 찾는 것이 된다.

에뮬레이터 수백 개를 띄운다. 하트비트 패킷만 흉내 내는 스크립트를 짠다. 한 기기에서 계정만 갈아 끼운다. 배터리도 데이터도 안 쓰고 보상만 가져간다. 정직하게 돌리는 사람은 상대적으로 손해를 보고, 결국 아무도 정직하게 돌리지 않는다.

DePIN에서 어려운 건 일을 시키는 게 아니라, 일했다는 걸 증명하게 만드는 것이다.

하트비트는 왜 거짓말하기 쉬운가 ​

가장 순진한 하트비트는 이렇게 생겼다 — 앱이 서버에게 현재 블록 높이를 묻고, 그 숫자에 서명해서 돌려보낸다. 서버는 서명을 확인하고 보상을 준다.

여기서 이 하트비트가 실제로 증명하는 건 무엇인가? "이 키를 가진 누군가가, 우리가 방금 알려준 숫자에 서명했다" 뿐이다. 폰일 필요도, 노드일 필요도, 심지어 앱일 필요도 없다. curl 한 줄이면 된다.

Before

서버
폰

증명되는 것 "누군가 알려준 숫자에 서명했다"

After

체인P2P
폰 · smoldot파이널리티 검증

증명되는 것 "이 기기가 체인을 직접 따라갔다"

검증의 위치가 하트비트의 의미를 바꾼다

그래서 검증을 폰으로 옮겼다. 라이트클라이언트가 체인에 직접 붙어 파이널리티를 확인하면, 하트비트가 서명하는 건 남이 알려준 숫자가 아니라 이 기기가 계산해낸 결과가 된다. 위조하려면 흉내가 아니라 실제로 라이트클라이언트를 돌려야 한다 — 그러면 그건 이미 일을 한 것이다.

그래도 한 겹으로는 부족하다 ​

검증을 폰으로 옮겨도 남는 구멍이 있다. 한 대의 강력한 기기가 라이트클라이언트 하나를 돌리면서 계정 수백 개에 같은 결과를 나눠 서명할 수 있다. 일은 한 번, 보상은 수백 번.

그래서 방어를 계층으로 쌓았다. 각 계층은 다른 질문에 답한다.

  • Layer 1 · 이 노드가 체인을 실제로 따라갔는가

    온디바이스 라이트클라이언트의 자체 검증. 흉내로는 통과할 수 없다.

  • Layer 2 · 이게 진짜 기기이고, 이 계정의 것인가

    하드웨어 키 어서션. 기기가 등록되고, 백엔드가 어서션을 검증한 하트비트만 인정한다. 에뮬레이터 군단과 계정 갈아끼우기를 여기서 막는다.

  • Layer 3 · 한 사람이 몇 대까지 이득을 보는가

    계정당 월 상한. 앞 두 계층을 모두 통과해도 무한히 늘릴 수는 없다.

핵심은 세 번째다. 기술적 방어는 언젠가 뚫린다고 가정해야 한다. 뚫렸을 때 피해가 선형으로 커지지 않도록, 마지막 방어선은 경제적이어야 한다. 상한이 있으면 우회에 드는 비용이 얻을 수 있는 보상을 금방 넘어선다.

실패를 정직하게 말하는 일 ​

이 구조에는 사용자 경험 쪽 함정이 하나 있다. 보상이 0이 되는 이유가 여러 개인데, 화면에는 보통 하나만 뜬다는 것이다.

월 상한에 걸려서 0인 것과, 기기 검증이 안 돼서 0인 것은 완전히 다른 상황이다. 앞의 것은 다음 달을 기다리면 되고, 뒤의 것은 지금 재인증을 해야 풀린다. 그런데 둘 다 "오늘 적립 0"으로 보이면, 검증이 막힌 사용자는 기다리기만 하다가 한 달을 날린다.

같은 숫자라도 원인이 다르면 다른 문장을 보여줘야 한다. 고칠 방법이 다르기 때문이다.

그래서 백엔드 게이트가 0을 반환할 때 이유 코드를 함께 내려보내고, 앱은 그 코드에 따라 다른 안내를 띄운다. 코드 주석에 규칙으로 박아뒀다 — 어테스테이션 문제는 절대 "월 상한 도달"로 표시하지 말 것.

배터리와 데이터가 진짜 제약이다 ​

여기까지가 설계라면, 실제로 사람들이 계속 켜두느냐는 완전히 다른 싸움이다. 폰에서 도는 노드는 두 가지 예산 안에서 살아야 한다 — 배터리와 모바일 데이터. 둘 중 하나만 넘어도 사용자는 앱을 지운다. 보상이 얼마든 상관없다.

초기 빌드는 시간당 90MB 넘게 썼다. 라이트클라이언트가 피어를 탐색하는 루프가 필요 이상으로 돌고 있었고, 체크포인트가 낡아서 매번 따라잡아야 할 구간이 길었다. 탐색 루프를 고치고 체크포인트를 갱신해 시간당 50MB 수준까지 내렸다.

이런 작업은 블로그 제목이 되지 않지만, DePIN 앱의 생사를 가르는 건 대개 이쪽이다. 아무도 "파이널리티 검증"때문에 앱을 지우지 않는다. 데이터 요금 때문에 지운다.

정리 ​

폰을 노드로 만드는 일은 세 개의 문제가 겹쳐 있다. 체인을 따라가게 만드는 문제(라이트클라이언트), 따라갔다는 걸 증명하게 만드는 문제(검증 위치 + 하드웨어 키), 그리고 계속 켜두게 만드는 문제(배터리와 데이터).

셋 중 하나라도 놓치면 나머지가 무의미해진다. 검증 없는 노드는 출석부고, 증명 없는 보상은 파밍장이고, 배터리를 먹는 앱은 아무도 안 켠다.

다음 글에서는 이 노드가 검증한 결과를 지갑 앱이 어떻게 가져다 쓰는지 — 즉 "서버에게 잔액을 묻지 않는 지갑"을 다룬다.