ShareText
글 쓰기
블록체인이 뭐야?
이 글에서는 블록체인의 거래 과정을 간단히 다룬다. 여러 블록체인의 전반적인 흐름은 같지만 검증 과정은 이더리움의 지분 증명을 기준으로 썼다. 검증자의 신뢰 문제는 글에 포함하지 않았으니, 일단은 검증자는 믿을만한 사람이라고 전제하고 넘어가보자. 우리가 은행에서 송금을 하는 것처럼, 누군가가 상태 변경을 요청한다(==). 그러면 이 요청이 네트워크를 타고 뿌려진다. 노드(=컴퓨터)에서 노드로. 노드는 요청의 대기열이 가지고 있기 때문에 전달받은 요청을 저장해뒀다가 다시 주변에 뿌린다. 노드 중에서 일부는 검증자이다. 검증자는 크게 두 역할을 하는데, 그중 하나는 블록을 만드는 역할이다. 이란 A4용지 한장처럼 정보를 묶은 단위이다. 아무 검증자나 블록을 만들 수 있는건 아니고, 매번 알고리즘을 통해 검증자가 지정된다. 그러면 검증자가 요청의 내용을 바탕으로 블록을 만들고, 주변 노드에게 뿌린다. 블럭을 만들때는 해시라는 과정을 거친다. 해시는 한쪽 방향으로만 가능한 변환이다. 즉, 결과값에서 원래 값을 역산할 수 없다. 원래 값을 알 수 없어도 문제는 없다. 왜냐면 어떤 값을 해시했을때의 결과가 기록된 값(=이전에 해시된 값)과 같다면 원래 값과 같다고 판정할 수 있기 때문이다. 그렇게 만들어진 블록은 네트워크를 타고 뿌려진다. 또다시 노드에서 노드로. 그리고 블록을 받은 검증자는 블럭을 뿌린다. 그리고 블록이 정상적이라면 본인의 서명도 뿌린다. 이것이 검증자의 둘째 역할이다. 노드가 서명을 받으면 어떤 블럭에 대한 서명인지를 파악하여 블럭에 붙여준다. 이렇게 쌓인 서명이 전체 검증자 수의 2/3를 넘으면 그때부터는 블럭을 확정한다. 이렇게 블럭을 받을 때마다 확정된 블럭이 사슬처럼 연결된다. 이것이 이다. 누군가 체인에 사이에 있는 블록을 조작하는 것은 현실적으로 어렵다. 이것은 해시와 관련이 있다. 해시를 할때 요청 받은 내용과 이전 블럭의 해시값을 사용한다. 즉, 만약에 누군가 특정 블록의 내용을 바꾼다면 그 블록의 해시값도 바꿔야 한다. 그러면 그 다음 블록의 해시값도 바뀌어야 한다. 왜냐면 다음 블록의 해시값도 이전 블록의 내용을 기반으로 만들어진 것이기 때문이다. 이런 식으로 조작한 블록 뒤에 있는 모든 블록의 해시값을 바꿔야 한다. 이 때문에 체인 중간에 있는 블록을 조작하기란 사실상 불가능하다. 다음 글에서는 검증자가 정말 믿어도 되는 사람들인지, 그리고 이더리움의 지분증명과 비트코인의 작업증명이 어떻게 다른지에 대해 알아보겠다.
이 글에서는 블록체인의 거래 과정을 간단히 다룬다. 여러 블록체인의 전반적인 흐름은 같지만 검증 과정은 이더리움의 지분 증명을 기준으로 썼다. 검증자의 신뢰 문제는 글에 포함하지 않았으니, 일단은 검증자는 믿을만한 사람이라고 전제하고 넘어가보자. 우리가 은행에서 송금을 하는 것처럼, 누군가가 상태 변경을 요청한다(==). 그러면 이 요청이 네트워크를 타고 뿌려진다. 노드(=컴퓨터)에서 노드로. 노드는 요청의 대기열이 가지고 있기 때문에 전달받은 요청을 저장해뒀다가 다시 주변에 뿌린다. 노드 중에서 일부는 검증자이다. 검증자는 크게 두 역할을 하는데, 그중 하나는 블록을 만드는 역할이다. 이란 A4용지 한장처럼 정보를 묶은 단위이다. 아무 검증자나 블록을 만들 수 있는건 아니고, 매번 알고리즘을 통해 검증자가 지정된다. 그러면 검증자가 요청의 내용을 바탕으로 블록을 만들고, 주변 노드에게 뿌린다. 블럭을 만들때는 해시라는 과정을 거친다. 해시는 한쪽 방향으로만 가능한 변환이다. 즉, 결과값에서 원래 값을 역산할 수 없다. 원래 값을 알 수 없어도 문제는 없다. 왜냐면 어떤 값을 해시했을때의 결과가 기록된 값(=이전에 해시된 값)과 같다면 원래 값과 같다고 판정할 수 있기 때문이다. 그렇게 만들어진 블록은 네트워크를 타고 뿌려진다. 또다시 노드에서 노드로. 그리고 블록을 받은 검증자는 블럭을 뿌린다. 그리고 블록이 정상적이라면 본인의 서명도 뿌린다. 이것이 검증자의 둘째 역할이다. 노드가 서명을 받으면 어떤 블럭에 대한 서명인지를 파악하여 블럭에 붙여준다. 이렇게 쌓인 서명이 전체 검증자 수의 2/3를 넘으면 그때부터는 블럭을 확정한다. 이렇게 블럭을 받을 때마다 확정된 블럭이 사슬처럼 연결된다. 이것이 이다. 누군가 체인에 사이에 있는 블록을 조작하는 것은 현실적으로 어렵다. 이것은 해시와 관련이 있다. 해시를 할때 요청 받은 내용과 이전 블럭의 해시값을 사용한다. 즉, 만약에 누군가 특정 블록의 내용을 바꾼다면 그 블록의 해시값도 바꿔야 한다. 그러면 그 다음 블록의 해시값도 바뀌어야 한다. 왜냐면 다음 블록의 해시값도 이전 블록의 내용을 기반으로 만들어진 것이기 때문이다. 이런 식으로 조작한 블록 뒤에 있는 모든 블록의 해시값을 바꿔야 한다. 이 때문에 체인 중간에 있는 블록을 조작하기란 사실상 불가능하다. 다음 글에서는 검증자가 정말 믿어도 되는 사람들인지, 그리고 이더리움의 지분증명과 비트코인의 작업증명이 어떻게 다른지에 대해 알아보겠다.
임해찬
2025년 12월 28일
조회 3
실패에 관하여
얼마 전 API 실패 처리를 누락하는 바람에 서비스의 일부 유저에게 망가진 UI를 보여주고 말았다. (무한 스켈레톤!) 친절한 상사님께서는 실수는 병가지상사라는 재밌는 표현으로 위로해주셨지만, 실패 처리에 관한 교훈을 뼈저리게 느꼈다.(왜 플레이스토어 심사는 3일씩 걸리는가 ㅠㅠ) 그래서 오늘은 여러 종류의 실패와 처리 방법에 대한 글을 적어보려고 한다. 프런트엔드 개발자의 흔한 고민은 초기값 설정이다. 비동기 작업(보통은 API 호출)을 통해 값을 얻어와야 하니 초기값을 무엇으로 두어야 하는지, 비동기 작업 실패는 어떻게 처리해야 할지 항상 애매하다. 1년차 개발자인 현재는 초기값은 null, 실패는 Exception 반환으로 처리하고 있다. null은 사람마다 받아들이는 의미가 다르겠지만 일반적으로 , 을 나타낸다고 생각한다. 즉, 값이 설정되기 전에는 값이 없다는 의미로 null을 사용하는 것이다. 한편 값을 받아오지 못한 경우에는 Exception을 반환하도록 하는 편인데, 비정상적인 상황이라는 의미와 그 이유(.를 활용) 를 표현할 수 있고, Kotlin의 을 활용하여 성공/실패를 가독성 좋게 표현할 수 있으며, Kotlin에서는 표현이 Nothing 타입이기 때문에 메서드의 반환형에 얽매일 필요가 없어진다. 실패했음을 의미하는 플래그(또는 합의된 값)을 사용하거나 억지로 null을 반환할 필요가 없으니 얼마나 편리한가? 모바일 개발자인 나는 플레이스토어와 앱 스토어같은 플랫폼 사업자들의 옥죄어 오는 손길을 느낀다. Android의 경우 버전에 따라 권한, 백그라운드 동작, 유저 정보 관리 등 하루가 다르게 정책이 촘촘해지고 있는데, 이에 따라 분기점도 늘고 있다. 권한은 그 중에서도 가장 민감하다. 이라는 주관적인 기준에 따라 허용 여부가 결정되기도 하고, 자칫 잘못하면 제재를 받을 수 있다. 그렇기에 유저에게 은밀하면서도 정직하게 권한 허용을 유도하기 위해 신경을 쓰는데, 권한 거부는 자칫 관심이 소홀해질 수 있는 시나리오다. (ex. 설마 거부하겠어? 이거 거부할거면 우리 앱 왜 써? 유저가 권한을 거부하더라도 핵심 권한이 아니라면 우회로를 생각해보자. 예를 들어 스타벅스 앱에서 주문할 매장 선택하기 버튼을 누르면 위치 권한을 요구하는데, 거부하면 위치 정보 접근이 허용되지 않았다며 매장명을 가나다 순으로 안내한다. 구글과 친해지는 것도 하나의 방법이다. 요즘 만보기 앱이 늘면서 다양한 앱이 걸음수를 세어 주는데, 구현 방식은 다양하다. 하드웨어 센서를 이용할 수도 있고(핸드폰 끄면 초기화!), GoogleFit API를 사용할 수도 있다(구글 로그인 필수). 핸드폰이 언제 꺼질까 걱정하며 5분마다 걸음수를 서버로 보낼 것인가? 아니면 유저가 구글 아이디가 있고, 구글에 우호적이기만을 바라며 앱에 필요도 없는 구글 로그인을 강요할 것인가? 이에 대해 구글이 제시하는 좋은 방법이 있다. Local Recording API를 사용하면 구글 로그인 없이도 걸음수를 얻어올 수 있다. 여전히 신체 활동 권한은 필요하고, 저장 기간도 최대 10일이지만 이만하면 만보기로는 충분하다. 중국의 격언이라고 한다. 어쩌면 개발자(엔지니어)는 문제를 가장 많이 만나는 사람들이 아닐까 싶다. 옆에 있는 사람들을, 나 자신을 다독여가며 실패를 잘 넘어가보자.
얼마 전 API 실패 처리를 누락하는 바람에 서비스의 일부 유저에게 망가진 UI를 보여주고 말았다. (무한 스켈레톤!) 친절한 상사님께서는 실수는 병가지상사라는 재밌는 표현으로 위로해주셨지만, 실패 처리에 관한 교훈을 뼈저리게 느꼈다.(왜 플레이스토어 심사는 3일씩 걸리는가 ㅠㅠ) 그래서 오늘은 여러 종류의 실패와 처리 방법에 대한 글을 적어보려고 한다. 프런트엔드 개발자의 흔한 고민은 초기값 설정이다. 비동기 작업(보통은 API 호출)을 통해 값을 얻어와야 하니 초기값을 무엇으로 두어야 하는지, 비동기 작업 실패는 어떻게 처리해야 할지 항상 애매하다. 1년차 개발자인 현재는 초기값은 null, 실패는 Exception 반환으로 처리하고 있다. null은 사람마다 받아들이는 의미가 다르겠지만 일반적으로 , 을 나타낸다고 생각한다. 즉, 값이 설정되기 전에는 값이 없다는 의미로 null을 사용하는 것이다. 한편 값을 받아오지 못한 경우에는 Exception을 반환하도록 하는 편인데, 비정상적인 상황이라는 의미와 그 이유(.를 활용) 를 표현할 수 있고, Kotlin의 을 활용하여 성공/실패를 가독성 좋게 표현할 수 있으며, Kotlin에서는 표현이 Nothing 타입이기 때문에 메서드의 반환형에 얽매일 필요가 없어진다. 실패했음을 의미하는 플래그(또는 합의된 값)을 사용하거나 억지로 null을 반환할 필요가 없으니 얼마나 편리한가? 모바일 개발자인 나는 플레이스토어와 앱 스토어같은 플랫폼 사업자들의 옥죄어 오는 손길을 느낀다. Android의 경우 버전에 따라 권한, 백그라운드 동작, 유저 정보 관리 등 하루가 다르게 정책이 촘촘해지고 있는데, 이에 따라 분기점도 늘고 있다. 권한은 그 중에서도 가장 민감하다. 이라는 주관적인 기준에 따라 허용 여부가 결정되기도 하고, 자칫 잘못하면 제재를 받을 수 있다. 그렇기에 유저에게 은밀하면서도 정직하게 권한 허용을 유도하기 위해 신경을 쓰는데, 권한 거부는 자칫 관심이 소홀해질 수 있는 시나리오다. (ex. 설마 거부하겠어? 이거 거부할거면 우리 앱 왜 써? 유저가 권한을 거부하더라도 핵심 권한이 아니라면 우회로를 생각해보자. 예를 들어 스타벅스 앱에서 주문할 매장 선택하기 버튼을 누르면 위치 권한을 요구하는데, 거부하면 위치 정보 접근이 허용되지 않았다며 매장명을 가나다 순으로 안내한다. 구글과 친해지는 것도 하나의 방법이다. 요즘 만보기 앱이 늘면서 다양한 앱이 걸음수를 세어 주는데, 구현 방식은 다양하다. 하드웨어 센서를 이용할 수도 있고(핸드폰 끄면 초기화!), GoogleFit API를 사용할 수도 있다(구글 로그인 필수). 핸드폰이 언제 꺼질까 걱정하며 5분마다 걸음수를 서버로 보낼 것인가? 아니면 유저가 구글 아이디가 있고, 구글에 우호적이기만을 바라며 앱에 필요도 없는 구글 로그인을 강요할 것인가? 이에 대해 구글이 제시하는 좋은 방법이 있다. Local Recording API를 사용하면 구글 로그인 없이도 걸음수를 얻어올 수 있다. 여전히 신체 활동 권한은 필요하고, 저장 기간도 최대 10일이지만 이만하면 만보기로는 충분하다. 중국의 격언이라고 한다. 어쩌면 개발자(엔지니어)는 문제를 가장 많이 만나는 사람들이 아닐까 싶다. 옆에 있는 사람들을, 나 자신을 다독여가며 실패를 잘 넘어가보자.
해찬
2025년 1월 3일
조회 4
제너레이터란
요즘은 알고리즘 문제를 푼 후에 ChatGPT에게서 피드백을 받는데, 종종 리스트 대신 제너레이터를 쓰라는 이야기를 듣는다. 이름은 많이 들어봤지만 무엇인지는 잘 몰라서 이 기회에 알아보려고 한다. 제너레이터는 지연 평가를 위한 장치다. 지연 평가란 함수를 실행하는 시점을 늦춘다는 이야기다. 함수가 실행되는 과정은 다음과 같다. 개발자가 소스 코드를 작성하고, 컴파일 타임에 해당 내용이 바이트 코드로 변환된다. 이후 개발자가 함수를 호출하면, 함수 프레임이 스택 영역에 쌓이고, 함수가 실행을 마친 후에는 함수 프레임이 스택 영역에서 제거된다. 즉, 함수 블록 내의 모든 코드는 함수를 호출했을 때 한 번에 실행된다. 하지만 어떤 경우에는 함수의 실행 시점을 제어해야 한다. 파일로부터 데이터를 읽어와서 화면에 표시해야 하는 경우, 얼마만큼의 용량을 읽어야 할까? 파일을 전부 다 읽으려고 하니까 메모리가 걱정된다. 그렇다고 일정량만 읽으려고 하니까 화면이 크면 데이터가 부족할 수 있어 걱정이다. 이런 때가 제너레이터를 쓸 때이다. 제너레이터는 원하는 때에 원하는 만큼만 실행할 수 있는 이터레이터이다. 그렇기 때문에 함수의 실행 시점을 제어할 수 있게 해준다. 사용 방식은 언어마다 약간씩 다르지만 대체로 비슷하다. 기존 함수는 키워드를 통해 값을 반환하고 함수를 종료했다면, 제너레이터 함수는 키워드를 통해 값을 반환하고 제너레이터 함수가 일시 종료된다. 일시 종료되었다는 말은 해당 제너레이터 함수를 다시 호출하면 이전 종료 시점부터 이어서 실행된다는 뜻이다. TypeScript와 Python에서도 사용 방법은 비슷하다. TypeScript에서는 대신 키워드를 쓰고 기존 반환 타입 를 로 바꾸면 된다. Python에서는 일반 함수에서 yield를 사용하면 자동으로 제너레이터가 되고, 리스트 컴프리헨션에서 대괄호(, ) 대신 소괄호(, )를 쓰면 된다. Kotlin의 경우 sequence 빌더가 타입 객체를 반환한다. 을 호출하여 next()를 직접 호출해도 되고, , 등의 함수를 사용해도 된다. TypeScript의 경우 타입 객체의 를 호출한다. Python의 경우 제너레이터 함수가 제너레이터 객체를 반환하고, 또는 와 같이 사용한다. 제너레이터 함수가 return을 호출한 이후 next()를 호출하는 경우 Kotlin과 Python에서는 예외가 발생한다(각각 , ). TypeScript에서는 를 반환한다. 세 언어 모두 for문을 사용하면 제너레이터 함수가 종료될 때까지 안전하게 순회한다. 사실 제너레이터로 할 수 있는 작업은 거의 대부분 리스트로도 할 수 있다. 그렇다면 제너레이터는 언제 써야할까? API 페이지네이션과 같이 결과 값을 저장할 필요 없이 한 방향으로 순회할 때, 또는 파일을 읽거나 무한 시퀀스를 만들 때처럼 메모리 효율이 중요할 때에도 제너레이터를 사용하면 좋다. 반면 데이터의 크기가 작은 경우, 데이터를 여러 번 순회하거나 인덱싱이 필요한 경우에는 리스트를 사용하는 것이 좋다. 제너레이터 함수는 이전 실행 상태를 기억한다. 그게 어떻게 가능한 걸까? 기본적으로 Kotlin, TypeScript, Python 모두 호출시에 함수 실행 정보를 저장한다. 다만 실행 정보를 저장하는 방식은 약간씩 다르다. Kotlin에서는 컴파일 타임에 제너레이터 함수를 상태 머신으로 변환한다. 즉, 제너레이터 함수를 상태에 따라 분기되는 일반 함수로 분해한다. 이후 런타임에 제너레이터 함수를 호출할 때마다 상태를 업데이트한 후에 제너레이터 함수를 실행한다. (이 역할은 Continuation 객체가 수행한다) TypeScript, Python에서는 런타임에 제너레이터 함수의 실행 정보를 제너레이터 객체에 저장한다. 제너레이터 함수를 재호출하면 제너레이터 객체에 저장된 정보를 스택 영역에 복원해서 이전 종료 지점부터 함수가 재개된다.
요즘은 알고리즘 문제를 푼 후에 ChatGPT에게서 피드백을 받는데, 종종 리스트 대신 제너레이터를 쓰라는 이야기를 듣는다. 이름은 많이 들어봤지만 무엇인지는 잘 몰라서 이 기회에 알아보려고 한다. 제너레이터는 지연 평가를 위한 장치다. 지연 평가란 함수를 실행하는 시점을 늦춘다는 이야기다. 함수가 실행되는 과정은 다음과 같다. 개발자가 소스 코드를 작성하고, 컴파일 타임에 해당 내용이 바이트 코드로 변환된다. 이후 개발자가 함수를 호출하면, 함수 프레임이 스택 영역에 쌓이고, 함수가 실행을 마친 후에는 함수 프레임이 스택 영역에서 제거된다. 즉, 함수 블록 내의 모든 코드는 함수를 호출했을 때 한 번에 실행된다. 하지만 어떤 경우에는 함수의 실행 시점을 제어해야 한다. 파일로부터 데이터를 읽어와서 화면에 표시해야 하는 경우, 얼마만큼의 용량을 읽어야 할까? 파일을 전부 다 읽으려고 하니까 메모리가 걱정된다. 그렇다고 일정량만 읽으려고 하니까 화면이 크면 데이터가 부족할 수 있어 걱정이다. 이런 때가 제너레이터를 쓸 때이다. 제너레이터는 원하는 때에 원하는 만큼만 실행할 수 있는 이터레이터이다. 그렇기 때문에 함수의 실행 시점을 제어할 수 있게 해준다. 사용 방식은 언어마다 약간씩 다르지만 대체로 비슷하다. 기존 함수는 키워드를 통해 값을 반환하고 함수를 종료했다면, 제너레이터 함수는 키워드를 통해 값을 반환하고 제너레이터 함수가 일시 종료된다. 일시 종료되었다는 말은 해당 제너레이터 함수를 다시 호출하면 이전 종료 시점부터 이어서 실행된다는 뜻이다. TypeScript와 Python에서도 사용 방법은 비슷하다. TypeScript에서는 대신 키워드를 쓰고 기존 반환 타입 를 로 바꾸면 된다. Python에서는 일반 함수에서 yield를 사용하면 자동으로 제너레이터가 되고, 리스트 컴프리헨션에서 대괄호(, ) 대신 소괄호(, )를 쓰면 된다. Kotlin의 경우 sequence 빌더가 타입 객체를 반환한다. 을 호출하여 next()를 직접 호출해도 되고, , 등의 함수를 사용해도 된다. TypeScript의 경우 타입 객체의 를 호출한다. Python의 경우 제너레이터 함수가 제너레이터 객체를 반환하고, 또는 와 같이 사용한다. 제너레이터 함수가 return을 호출한 이후 next()를 호출하는 경우 Kotlin과 Python에서는 예외가 발생한다(각각 , ). TypeScript에서는 를 반환한다. 세 언어 모두 for문을 사용하면 제너레이터 함수가 종료될 때까지 안전하게 순회한다. 사실 제너레이터로 할 수 있는 작업은 거의 대부분 리스트로도 할 수 있다. 그렇다면 제너레이터는 언제 써야할까? API 페이지네이션과 같이 결과 값을 저장할 필요 없이 한 방향으로 순회할 때, 또는 파일을 읽거나 무한 시퀀스를 만들 때처럼 메모리 효율이 중요할 때에도 제너레이터를 사용하면 좋다. 반면 데이터의 크기가 작은 경우, 데이터를 여러 번 순회하거나 인덱싱이 필요한 경우에는 리스트를 사용하는 것이 좋다. 제너레이터 함수는 이전 실행 상태를 기억한다. 그게 어떻게 가능한 걸까? 기본적으로 Kotlin, TypeScript, Python 모두 호출시에 함수 실행 정보를 저장한다. 다만 실행 정보를 저장하는 방식은 약간씩 다르다. Kotlin에서는 컴파일 타임에 제너레이터 함수를 상태 머신으로 변환한다. 즉, 제너레이터 함수를 상태에 따라 분기되는 일반 함수로 분해한다. 이후 런타임에 제너레이터 함수를 호출할 때마다 상태를 업데이트한 후에 제너레이터 함수를 실행한다. (이 역할은 Continuation 객체가 수행한다) TypeScript, Python에서는 런타임에 제너레이터 함수의 실행 정보를 제너레이터 객체에 저장한다. 제너레이터 함수를 재호출하면 제너레이터 객체에 저장된 정보를 스택 영역에 복원해서 이전 종료 지점부터 함수가 재개된다.
Kotlin
Python
Javascript
해찬
2025년 8월 24일
조회 3
블록체인 검증은 믿을만할까?
이 글에서는 이전 글에서 예고했듯이 검증 과정이 정말 믿을만한 지에 대해 다룬다. 비트코인은 작업 증명 방식을 사용한다. 여기서 작업이란 블록을 만들기 위해서 필요한 연산을 의미한다. 블록을 만들기 위해서는 특정한 조건에 맞는 해시값을 찾아야 하는데(=해시퍼즐), 찾는 시간이 약 10분 정도 걸리도록 난이도가 조절된다. 따라서 유효한 블록을 만드는 것 자체가 굉장히 어렵기 때문에 가짜 체인을 만드는 것이 사실상 불가능해지는 것이다. 만약 어느 두 컴퓨터가 거의 동시에 해시퍼즐을 풀었다고 해보자. 해시퍼즐을 푼 두 컴퓨터가 각각 A, B 라는 블록을 만들고, 이를 주변 노드(=컴퓨터)에게 전달한다. 그러면 노드들은 먼저 받은 블록을 저장하기 때문에, 네트워크에는 일시적으로 두 체인이 존재하게 된다(A 로 끝나는 체인과 B 로 끝나는 체인). 그러다가 시간이 지나면서 두 체인의 길이가 달라지면 노드들은 더 긴 체인을 채택하기 때문에, 다른 한쪽의 체인은 버려진다. 따라서 두 컴퓨터가 거의 동시에 해시퍼즐을 푼 경우에도, 더 많은 노드에 의해 빠르게 저장된 블록만이 살아남는다. 그래서 비트코인을 채굴하더라도, 한참 뒤에 해당 블록이 버려지지 않았다는 것이 사실상 확실해지는 시점(약 100블록 후)에야 보상을 사용할 수 있다. 이더리움은 지분 증명 방식을 사용한다. 이 방식은 자신이 보유한 지분을 담보로 맡긴 사람에게 검증자 자격을 부여하는 방식이다. 무작위로 선택된 검증자가 블럭을 생성하면 다른 검증자가 생성된 블럭이 유효한지 확인하고 서명한다. 그리고 전체 검증자의 2/3 이상의 서명을 받은 블럭은 유효한 블럭으로 확정된다. 이 수치는 에서 유래된 값으로, 악의적인 사용자가 1/3 이상을 넘지 않을 것이라는 전제에 근거한다. 정말로 악의적인 사용자(블럭의 유효 여부를 조작하는 사용자)가 1/3 이상을 넘을 수 없을까? 2025년 기준 이더리움의 검증자가 100만명을 넘으므로, 어림잡아 30만명 이상의 검증자가 담합해야 한다. 금액으로 따지면 검증자의 최소 지분은 32ETH(한화 약 1.5억원)이므로 수십조원에 달하는 금액이다. 이에 비추어 생각해볼때 악의적인 검증자가 전체의 1/3을 넘기는 현실적으로 어려워 보인다. 다음 글에서는 비트코인, 이더리움 등 여러 블록체인 간에 어떤 차이점이 있는지, 왜 서로 다른 블록체인들이 탄생하게 되었는지를 알아보겠다.
이 글에서는 이전 글에서 예고했듯이 검증 과정이 정말 믿을만한 지에 대해 다룬다. 비트코인은 작업 증명 방식을 사용한다. 여기서 작업이란 블록을 만들기 위해서 필요한 연산을 의미한다. 블록을 만들기 위해서는 특정한 조건에 맞는 해시값을 찾아야 하는데(=해시퍼즐), 찾는 시간이 약 10분 정도 걸리도록 난이도가 조절된다. 따라서 유효한 블록을 만드는 것 자체가 굉장히 어렵기 때문에 가짜 체인을 만드는 것이 사실상 불가능해지는 것이다. 만약 어느 두 컴퓨터가 거의 동시에 해시퍼즐을 풀었다고 해보자. 해시퍼즐을 푼 두 컴퓨터가 각각 A, B 라는 블록을 만들고, 이를 주변 노드(=컴퓨터)에게 전달한다. 그러면 노드들은 먼저 받은 블록을 저장하기 때문에, 네트워크에는 일시적으로 두 체인이 존재하게 된다(A 로 끝나는 체인과 B 로 끝나는 체인). 그러다가 시간이 지나면서 두 체인의 길이가 달라지면 노드들은 더 긴 체인을 채택하기 때문에, 다른 한쪽의 체인은 버려진다. 따라서 두 컴퓨터가 거의 동시에 해시퍼즐을 푼 경우에도, 더 많은 노드에 의해 빠르게 저장된 블록만이 살아남는다. 그래서 비트코인을 채굴하더라도, 한참 뒤에 해당 블록이 버려지지 않았다는 것이 사실상 확실해지는 시점(약 100블록 후)에야 보상을 사용할 수 있다. 이더리움은 지분 증명 방식을 사용한다. 이 방식은 자신이 보유한 지분을 담보로 맡긴 사람에게 검증자 자격을 부여하는 방식이다. 무작위로 선택된 검증자가 블럭을 생성하면 다른 검증자가 생성된 블럭이 유효한지 확인하고 서명한다. 그리고 전체 검증자의 2/3 이상의 서명을 받은 블럭은 유효한 블럭으로 확정된다. 이 수치는 에서 유래된 값으로, 악의적인 사용자가 1/3 이상을 넘지 않을 것이라는 전제에 근거한다. 정말로 악의적인 사용자(블럭의 유효 여부를 조작하는 사용자)가 1/3 이상을 넘을 수 없을까? 2025년 기준 이더리움의 검증자가 100만명을 넘으므로, 어림잡아 30만명 이상의 검증자가 담합해야 한다. 금액으로 따지면 검증자의 최소 지분은 32ETH(한화 약 1.5억원)이므로 수십조원에 달하는 금액이다. 이에 비추어 생각해볼때 악의적인 검증자가 전체의 1/3을 넘기는 현실적으로 어려워 보인다. 다음 글에서는 비트코인, 이더리움 등 여러 블록체인 간에 어떤 차이점이 있는지, 왜 서로 다른 블록체인들이 탄생하게 되었는지를 알아보겠다.
작업증명
지분증명
임해찬
2025년 12월 29일
조회 4