geth 소스 읽기 Part2 Day01steemCreated with Sketch.

in kr •  6 months ago

main_logo

이 글은 go-ethereum(geth 클라이언트) 소스 읽기 시리즈 Part2 연재 중 첫 번째 글입니다. 전체 연재 목록은 아래 페이지에서 확인해 주세요.
http://www.notforme.kr/block-chain/geth-code-reading

Part2 연재의 대상 독자 및 목표

이 글은 독자 분들이 적어도 Java와 같은 OOP 계열의 언어로 프로그래밍 경험이 있다는 것을 가정합니다. 또한 계정, 채굴 등 블록체인과 이더리움과 관련된 기초적인 개념과 이더리움 백서나 황서의 내용을 간단하게 알고 있다고 가정합니다.

연재의 목표는 geth 코드를 읽으면서 백서 및 황서에서 정의된 스펙이 어떻게 구현되었는지를 확인하는 것입니다. 더불어 이 연재는 다음 3가지 목적을 염두하고 쓴 것입니다.

  1. 새로운 언어(Go)를 오픈소스 코드를 읽으며 배운다.
  2. 오픈소스를 읽으며 코드리딩 능력을 배양한다.
  3. 블록체인의 기술을 직접 코드를 통해서 익힌다.

지난 Part1에서는 총 6번의 글을 통해서 geth구조와 실행방법에 대해서 살펴봤습니다. Part2에서는 좀 더 구체적으로 이더리움의 핵심 기능들이 어떻게 구현되었는지 Part1에서와 마찬가지로 코드를 통해서 알아보려고 합니다

다루는 내용

오늘은 블록체인의 꽃인 마이닝 코드를 분석하기 위한 준비 작업으로 실습을 해봅니다. 로컬 환경에 geth를 실행한 후 마이닝을 해보고 콘솔로 접속하여 사용자의 잔액 확인, 이더리움 전송하기 등을 실습해 볼 예정입니다. 오늘 이 글의 실습이 실제 마이닝 과정이 코드에서 어떻게 진행되는지 살펴보는데 도움이 될 것입니다.

오늘 실습은 지난 Part 1의 연재에서 VS Code를 사용했던 것과 달리 Jetbrains에서 나온 Goland를 IDE로 사용하였습니다. 실습을 따라해 볼 분들은 사이트에서 30일 무료 평가판을 다운 받아서 사용하시면 됩니다.


Goland IDE에서 디버깅 모드로 실행하기

소스를 다운받아서 Goland로 프로젝트를 열면 소스에 main 함수가 있을 경우 아래와 같이 실행버튼이 보이게 됩니다.

d01_main.png

지난 Part1에서 분석한 geth 커맨드의 최초 진입점이 되는 main 함수의 실행 버튼을 클릭하면gethRun 또는 Debug로 실행 시킬 수 있습니다.

d01_run.png

하지만 이대로 실행하며 다음과 같은 오류를 만나게 됩니다.

d01_error.png

이는 main.go 파일 하나만 참조해서 빌드하려다보니 참조에러가 난 것인데요. Goland의 실행환경 셋팅을 변경해 주면 해결할 수 있습니다. 메뉴 Run > Edit Configuration을 실행하면 방금 실행에러가 났던 Go Build 환경이 있을 것입니다. 이 환경을 선택한 후에 실행 방법을 수정해야 합니다.

d01_run_setting.png

실행오류가 났던 설정은 Run kind가 파일로 선택되어 있을텐데요. 이를 위 그림과 같이 디렉토리로 변경하고 main.go가 있는 cmd/geth 인 것을 확인해야 합니다. 추가로 Program arguments에는 터미널에서 geth를 실행할 때 전달하는 옵션을 동일하게 줘야 합니다. 여기서는 프라이빗 네트워크로 실행할 것이기 때문에 —nodiscover를 줬습니다.

이제 다시 실행하면 정상적으로 Goland에서 geth를 직접 실행되는 것을 확인할 수 있습니다.

로컬 테스트 네트워크 실행

로컬 테스트 네트워크 환경은 이더리움 공식 문서에 있는 Test Networks를 참조하여 진행합니다. 매뉴얼을 기반으로 실습에서 로컬 네트워크를 위해 필요한 것은 아래와 같습니다.

  1. 커스텀 genesis.json 파일
  2. 커스텀 Data 디렉토리

공식 매뉴얼에는 커스텀 네트워크 ID를 요구하고 있지만 여기서는 --nodiscover 옵션을 기본으로 줄 예정이라 커스텀 네트워크ID를 생략해도 됩니다. 그럼 차근히 로컬 네트워크를 구축해 봅시다.

genesis.json 준비

genesis.json은 테스트 네트워크의 블록체인의 첫번째 블록 정보를 담고 있습니다. 공식 매뉴얼에 샘플이 있습니다만… 최신버전을 따라가지 못해서 현재 버전의 geth에서는 사용할 수 없습니다. 매뉴얼에 빠져 있는 json의 키 중에 하나는 config입니다. config가 포함된 커스텀 genesis.json 파일이 아래와 같이 준비되어야 합니다.

{
    "config": {},
    "nonce": "0x0000000000000042",
    "timestamp": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": "0x00",
    "gasLimit": "0x8000000",
    "difficulty": "0x400",
    "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x3333333333333333333333333333333333333333",
    "alloc": {}
}


각 속성에 대한 상세한 내용은 (비록 영어지만) 다음 링크의 설명으로 위임하겠습니다.

https://gist.github.com/0mkara/b953cc2585b18ee098cd#file-genesis-md

커스텀 디렉토리에 genesis.json 초기화

이제 genesis.json이 준비 되었으니 이 파일로 테스트 네트워크의 블록체인을 초기화할 준비가 되었네요. 초기화 커맨드는 굳이 IDE에서 디버깅 모드로 실행하지 않고, 이미 컴파일한 geth 바이너리로 다음과 같이 명령을 입력하여 초기화 합니다.

geth --datadir ./node1  init genesis.json


이 명령 가운데 중요한 부분은 --datadir 옵션과 함께 커스텀 디렉토리의 경로를 넘긴 것입니다. 명령이 정상적으로 실행되었다면 Successfully wrote genesis state로그를 볼 수 있고 커스텀 디렉토리로 준 경로 node1에 따라 실제 네트워크의 데이터가 저장될 node1 디렉토리가 생긴 것을 확인할 수 있습니다.

테스트 네트워크 설정으로 geth 실행

이제 Goland에서 geth를 실행할 차례입니다. 앞서 Program Argumentsnodiscover 옵션 하나만 주었는데요. 테스트 네트워크의 노드로 실행하기 위해서 공식 매뉴얼에 따라 다음과 같이 몇가지 옵션을 추가합니다.

--identity
"node1"
--nodiscover
--rpc
--rpcapi
"db,eth,net,web3"
--rpcport
8545
--rpccorsdomain
"*"
--datadir
"커스텀 디렉토리 경로"


새로 추가된 옵션은 geth를 컨트롤하기 위한 rpc관련 설정들입니다. 이는 바로 이어서 마이닝에 따라 블록 생성과정을 확인할 때 필요한 설정이기도 합니다. 위 옵션을 추가했다면 이제 Goland에서 geth를 디버깅 환경으로 실행합니다. 역시 문제가 없다면 geth가 실행된 상태로 다음과 같이 대기하고 있게 됩니다.

d01_run_scc.png

콘솔로 접근하여 마이닝 시작하기

이제 geth 노드 하나가 IDE의 디버깅 환경으로 실행된 상태입니다. 이 상태에서 터미널을 하나 열어서 해당 프로세스에 명령을 줄 수 있도록 콘솔로 접근합니다. 콘솔 접근은 간단합니다. 현재 실행 중인 geth의 커스텀 디렉토리를 동일하게 맞춰서 다음과 같은 명령으로 콘솔을 실행합니다.

geth attach --datadir ./node1


정상적으로 콘솔이 실행되면 >와 같은 프롬프트가 보입니다. 이 프롬프트는 자바스크립트 런타임 환경이라고 보면 됩니다. 이더리움은 geth의 명령을 rpc 형태로 제공하고 있고 web3라는 자바스크립트 라이브러리를 사용하여 이더리움의 rpc를 손쉽게 호출할 수 있는데요. 이 때문에 geth에서 자바스크립트 런타임환경을 제공하고 있는 것입니다.

이제 이 프롬프트를 통해서 web3의 명령을 입력할 수 있습니다. 한 번 마이닝을 시도해 볼까요? 다음 명령을 콘솔에 입력합니다.

> miner.start()


안타깝게도 이 명령은 다음과 같은 에러와 함께 실행되지 않습니다.

d01_miner_fail.png

마이닝을 통해서 생성된 이더리움을 귀속시킬 계정인 etherbase가 없기 때문입니다. 그럼 계정을 하나 만들어 봅시다. 다음 그림과 같이 web3personal.newAccount() 함수를 이용하면 손쉽게 계정을 만들 수 있습니다.

d01_new_account.png

이제 다시 miner.start()를 호출하면 정상적으로 호출이 성공하고 IDE의 로그 창에서 다음과 같은 로그를 확인할 수 있습니다.

d01_mining_ok.png

블록 생성에 따라 획득한 이더리움이 위에서 생성한 계정으로 들어온 것을 확인할 때도 역시 web3를 사용하면 됩니다. 예를 들면 다음과 같이 확인할 수 있습니다.

d01_ether_check.png

eth.getBalance() 함수는 사용자 계정의 잔액을 반환하는데 반환된 값의 단위가 이더리움이 아닙니다. 따라서 web3.fromWei()함수로 단위변화를 해야 이더리움 단위로 잔액을 조회할 수 있습니다.

마이닝을 중지하려면 콘솔에 miner.stop() 을 실행하면 됩니다. 제 실습 장비는 맥북프로인데 마이닝을 켜고 한 5분정도만 지나면 금방 팬돌아가는 소리가 납니다. PoW 과정에서 CPU소모가 있으니 마이닝 과정 확인이 필요할 때만 실행시키면 됩니다.

Mist로 테스트 네트워크의 계정 확인하기

Mist는 이더리움 재단에서 관리하는 지갑 및 Dapp 브라우저입니다. 여기서는 위에서 생성한 계정의 정보를 확인하고 스마트 컨트랙트를 간단히 확인해 볼 용도로 설치해서 사용해 보려고 합니다. 다음 링크에 들어가 해당 운영체제에 맞는 바이너리 파일을 받아서 설치합니다.

https://github.com/ethereum/mist/releases

이제 Mist를 실제 이더리움의 메인네트워크가 아닌 바로 위에서 실행한 테스트 네트워크로 붙어서 실행할 수 있게 해야하는데요. 방법은 터미널에서 Mist를 실행하면서 옵션으로 테스트 네트워크의 프로세스 정보와 커스텀 디렉토리를 넘기면 됩니다. 제가 실습하고 있는 환경인 Mac을 기준으로 터미널에서 다음과 같이 Mist를 실행하면 됩니다.

/Applications/Ethereum\ Wallet.app/Contents/MacOS/Ethereum\ Wallet --rpc ./node1/geth.ipc  --node-datadir ./node1


바로 위에서 실행한 node1 경로로 Mist를 실행시켰습니다. 잠시 후 다음과 같은 창이 뜨는 것을 볼 수 있습니다.

d01_mist.png

팝업 창 오른쪽 상단에 테스트 네트워크라는 표시가 PRIVATE_NET으로 되어 있는 것을 확인할 수 있습니다. 이제 LAUNCH APPLICATION 을 클릭하면 다음과 같이 콘솔에서 생성한 계정의 잔고를 Mist에서 확인할 수 있네요.

d01_mist_browse.png

explorer를 활용하여 블록 생성 이력 확인하기

한가지 더 나아가 볼까요. 매 블럭이 어떻게 생성되었는지 조금 더 편하게 확인할 수 있습니다. github에 공개된 explorer라는 웹앱을 설치하면 되는데요. 우선 자바스크립트와 관려된 툴인 npm, bower만 설치되어 있으면 손쉽게 사용할 수 있습니다. github에 명시된대로 순차적으로 다음 명령을 실행하면 됩니다.

$ git clone https://github.com/carsenk/explorer`
$ cd explorer
$ npm install
$ bower install
$ npm start


앞서 geth를 실행할 때 rpc 설정으로 포트를 8545지정해 두었는데요. 사실 바로 이것 때문에 포트를 8545로 지정한 것입니다. npm start가 정상적으로 실행되었다면 노드 서버로 explorer 웹앱이 8000 포트로 실행된 것을 터미널에서 확인할 수 있습니다.

d01_explorer.png

이제 8000포트로 브라우저에 접근해 봅시다. 다음과 같이 현재 테스트 네트워크의 블록 생성 정보를 쉽게 확인할 수 있는 웹앱이 보이면 정상입니다.

d01_explorer_block.png

이더리움 보내기

이 글의 마지막 과업(?)이 남았습니다. 지금까지 마이닝의 베이스인 한 명의 계정만 있었는데 다른 사용자 계정으로 이더리움을 전송하는 것입니다. 이더리움을 전송하는 것은 Mist에서도 할 수 있지만 여기서는 콘솔로 실습해 보겠습니다. 콘솔이 띄워진 상태에서 personal.newAccount()로 이더리움을 전송받을 계정을 추가로 하나 더 만들어 둡니다. 그럼 이제 총 2명의 계정이 있겠지요? 이제 다음 명령을 순차적으로 입력합니다.

> var u1 = eth.accounts[0]
> var u2 = eth.accounts[1]
> var amount = web3.toWei(10, "ether")
> var tx = {from:u1, to:u2, value: amount}
> personal.unlockAccount(u1)
// 사용자 u1의 비번 입력
> eth.sendTransaction(tx)


위 스크립트의 내용은 다음과 같습니다. 먼저 u1과 u2 변수에 각각 이더리움을 보낼 사람과 받을 사람의 주소를 담았습니다. amount에 10 이더를 선언한 후 이더리움 전송을 위한 tx 변수를 선언했습니다. 이 상태에서 eth.sendTransaction(tx) 를 호출하면 실패합니다. 보내는 계정이 잠겨있기 때문에 한번 풀어줘야 합니다. 따라서 personal.unlockAccount(u1) 을 호출하여 비번을 입력한 후 계정을 풀어야 트랜잭션을 등록할 수 있습니다.

정상적으로 성공했다면 받는 사람 계정에 10 이더가 전송되었겠지요? 만약 마이닝이 진행중이 아니라면 받는 사람 잔고에 10이더는 들어있지 않을 것입니다. 마이닝이 진행되고 해당 트랜잭션이 블록에 포함되었다면 콘솔에서 u2 의 잔고를 eth.getBalance(u2)를 호출하여 확인할 수 있습니다. 또는 explorer에서도 다음과 같이 확인할 수도 있습니다.

d01_explorer_tx.png

디버깅 잠깐 맛보며 마무리

오늘은 geth의 마이닝과정 코드를 살펴보기에 앞서 Goland의 디버깅 환경과 함께 실제 마이닝 작업을 로컬 테스트 네트워크로 실행해 봤습니다. 다음 글에서는 오늘 진행한 과정을 코드 레벨에서 살펴볼 예정입니다.

이제 오늘 셋팅한 디버깅환경을 맛을 보며 오늘 글을 마치고자 합니다. geth 소스 miner 폴더에 있는 miner.go파일을 열어 self.worker.start() 코드의 위치를 찾아보세요. 그리고 이 라인에 디버그 포인트를 찍은 후 콘솔에서 miner.start()를 실행해 봅시다. 아마 마이닝이 시작되기 전에 우리가 잡은 디버그 포인트에서 멈춘 후 다음과 같은 화면을 Goland에서 볼 수 있을 것입니다.

d01_goland_debug.png

IDE로 디버깅 포인트를 잡을 수 있으면 오늘 실습해 본 마이닝 과정을 진행해보면서 코드 흐름을 추적하기도 쉽습니다. 이제 다음 글에서 직접 코드를 살펴보도록 하겠습니다.

그럼 다음 연재에서 뵙겠습니다. ^^

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

귀한 내용 감사합니다. ^^

·

꾸준히 올려보도록 하겠습니다 :)

글 잘읽었습니다. 이렇게 소스코드 분석해가면서 보면 정말 많은 도움이 될 것 같아요 :)

·

소스 분석하면서 공부한 것 정리겸 공유하는데 읽기 편하게 쓰기가 쉽지 않네요 ㅜ
직접 코드 분석하는데 도움이 될 수 있도록 종종 올릴게요 :)

vscode로 소스 열어서 디버깅 연습하고 있습니다.
혹시 launch.json 파일 상세를 공유 받을 수 있을까요?
쉘로 geth를 실행하면 이후에 attach 등의 작업이 잘 되는데,
vscode에서 똑같은 옵션으로 실행하면 조금 실행되다가 죽어버리네요..
(rpc 연결 문제인듯 합니다)
혹시 제가 launch.json 파일을 잘 못 작성했나 해서 도움 요청 드립니다!