How does go-ethereum work? 박정원(Aiden) aiden.p@onther.io
P2P Network 2) Mining Ethereum? Consensus Not only PoW! broadcasting (ex. transaction, block) syncing (ex. transaction, block) state transition (transaction execution) Consensus
Why? 2) Mining Ethereum? state transition (transaction execution) go-ethereum의 핵심이기도 하지만, 현재 PoC단계인 Plasma-EVM을 살펴볼때도 큰 도움이 되기 때문.
본격적으로 시작하기 전에... Go 언어 자체에 대한 설명 go-ethereum의 전체 실행로직에 대한 자세한 설명 위 두가지는 최대한 압축적으로 정보를 전달해 드리기 위해 본 발표에서 최소한으로 다루겠습니다.
공부 자료 https://steemit.com/@sigmoid sigmoid 님의 go-ethereum 분석기 : geth의 거의 모든 부분을 잘 정리해주셨습니다. 개인적으로 정말 많은 도움이 되었습니다. https://github.com/NAKsir-melody/go-ethereum sigmoid 님의 go-ethereum 주석 한글화 프로젝트 http://www.notforme.kr/block-chain/geth-code-reading 자바 개발자의 go-ethereum 소스 분석기 : geth의 entry point부터 차근차근 잘 설명해주셨습니 다. 처음 공부를 시작하시는 분들은 이 분의 글을 먼저 보시면 큰 도움이 되실겁니다. https://blog.seulgi.kim/ 코드박스 김슬기님의 블로그 : 이더리움에 대한 수준 높은 포스트가 정말 많습니다. 직접적으로 소스코드를 다루는 글은 없지 만 이더리움의 기술적인 부분을 이해하시는데 정말 큰 도움이 됩니다. https://tech.etherstudy.net/ethereum/geth/delve/debug/consensus/lifecycle/2018/08/02/geth-consensus-lifecycle-debug.html 임완섭님의 이더리움 컨 센서스 라이프사이클 디버깅 : 디버깅을 통해 소스코드를 보다 더 이해하기 쉽게 해주셨습니다. 역시 큰 도움이 되실겁니다.
0. 전체 흐름
Geth? Geth Node …. Ethereum service Shh service EthStats Service Dashboard service EthStats Service Txpool Miner Blockchain Protocol Manager …. Reference https://steemit.com/etherum/@sigmoid/55fbja
cmd/geth/main.go | func geth() func geth() 가 바로 geth 노드를 생성하고 실행시키는 함수이다. makeFullNode() : Geth 노드 생성 startNode() : Geth 노드 실행
miner 빨간색으로 표시된 부분인 services 가 바로 앞에서 살펴본 여러 서비스들이 등록되는 곳이다. serviceFuncs는 해당 service들의 Constructor역할을 하는 함수들이다. makeFullNode()과정에서 등록된 서비스들의 Constructor가 startNode()에서 실행되어 비로소 해당 서비스가 services에 할당된다.
cmd/geth/config.go | func makeFullNode() 중요!! 노드를 생성하는 함수인 makeFullNode를 살펴보면 RegisterEthService, RegisterDashboardService, RegisterShhService, RegisterEthStatsService 들을 실행시키는 것을 알 수 있다. 하지만 여기서 이더리움 노드 서비스를 생성하는 RegisterEthService()를 제외한 나머지는 크게 중요하지 않다. (Dashboard, whisper, Stats 관련 설정이다) 만약 해당 서비스들에 관심이 없다면 굳이 자세히 들여다 볼 필요는 없다.
1. miner 기능 생성과 시작
miner/miner.go | func New() eth/backend.go | func New() 실행과정 : 이더리움 풀노드 서비스 생성과정 이더리움 풀노드 서비스를 생성(Construct)하는 과정에서 마이너 기능 또한 설정이 된다. 60 : 생성과정에서 newWorker()를 호출한다. (이름에서 알 수 있듯이 worker가 실질적인 마이닝 작업의 대부분을 처리한다. 63 : 블록을 sync하는 Downloader의 이벤트를 추적하며 그에 맞는 로직을 수행한다.
miner/worker.go | func newWorker() worker가 실질적으로 mining의 대부분의 로직을 처리한다. 143~159 : worker 를 생성한다. newWorkch, taskCh, resultCh, exitCh, startCh 등의 채널을 생성한다. 161 : newTXsEvent를 txpool로부터 구독 163~164 : 블록체인의 체인 헤드와 체인사이드 이벤트를 구독 166 : 블록을 생성하는 메인 루프 로직이다. 167 : 새로운 마이닝 작업을 시작하게 하는 루프 로직이다. 168 : 작업이 끝난 블록을 데이터베이스에 저장하는 역할을 하는 루프다. 169 : 컨센서스 엔진에 task를 전달하기 위한 루프(sealing하는 로직을 실행 후 resultLoop로 전달하게 된다)
네가지 루프(고루틴)의 실행로직을 이해하는게 핵심! Mining의 실행로직(큰 그림) 이더리움노드 서비스 실행과정에서 startMining() 실행 Miner기능을 시작하는 miner.Start() 실행 worker.startCh 채널로 신호 전달 newWorkLoop() 에서 newWorkReq를 worker.newWorkCh 채널로 전달 mainLoop() 에서 task를 worker.taskCh 채널로 전달 taskLoop() 에서 Sealing이 완료된 블록을 worker.resultCh 채널로 전달 resultLoop() 에서 블록을 체인에 삽입(insert) 및 전파한 이후 블록이 새롭게 생성되었다는 메세지를 newWorkLoop()로 전달 네가지 루프(고루틴)의 실행로직을 이해하는게 핵심!
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
고루틴(Goroutine) & 채널(Channel) Reference http://pyrasis.com/book/GoForTheReallyImpatient/Unit01/05 http://rapapa.net/?p=2704
eth/backend.go | func StartMining() 337~341 : 이더베이스(코인베이스 계정) 계정을 가져온다. 342~349 : engine이 Clique(PoA)일 경우에 앞서 구한 이더베이스 계정을 통해 Authorize를 한다. 357 : 이더베이스를 인자로 전달하여 고루틴으로 miner.Start()메소드 실행
miner/miner.go | func Start() 109 : 이더베이스 관련 설정(코인베이스 계정 설정)을 진행한다. 111~114 : 만약 canStart 가 0이라면 아직 네트워크 동기화가 진행중임을 의미하므로 로그를 찍고 miner의 실행을 중단한다. 115 : Miner.worker의 start() 메소드를 실행한다.
miner/worker.go | func start() 채널이 굉장히 중요! 212 : worker가 스타트했다는 의미로 &w.running에 1을 할당한다. 213 : w.startCh 채널에 신호를 보낸다. (worker의 newWorkLoop로 이어진다.)
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
miner/worker.go | func newWorkLoop() 먼저 newWorkReq를 newWorkCh 채널로 전달하는 로직이 포함된 함수를 commit 변수에 할당한다. 이 함수는 새로운 마이닝 작업의 시작을 알리는 역할을 한다.
miner/worker.go | func newWorkLoop() 앞서 startMining()의 실행결과 w.startCh로 신호를 보내는 것을 알 수 있었다. 347~350 : 해당 신호가 들어오면 commit()을 실행하게 된다. 352~355 : 마이닝 사이클이 한번 돌고 난 뒤 다시 마이닝을 시작하는 부분
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
miner/worker.go | func mainLoop() mainLoop 고루틴이 실행되면 계속해서 for문 루프를 돌게 된다. 앞의 newWorkLoop()를 통해 w.newWorkCh 채널에 값이 들어오게 되면 w.commitNewWork()를 호출하여 새로운 블록의 생성을 시작하게 된다.
miner/worker.go | func commitNewWork() 655~667 : 블록 시간 체크 (너무 시간이 많이 흐르지 않도록) 669~676 : 새로운 헤더 생성, 헤더 Number에 부모 Number + 1, 가스리밋 계산 등
miner/worker.go | func commitNewWork() 678~684 : 코인베이스 주소 설정 (만약에 비어있다면 에러) 685~688 : (PoW일 경우)헤더가 ethash 프로토콜을 따르도록 난이도 필드를 초기화 한다. 689~701 : DAO 해킹관련 하드포크를 했기 대문에, 해당 구간의 블록은 따로 검증한다.
miner/worker.go | func commitNewWork() 744 : Pending 상태인 트랜잭션들을 TxPool 에서 가져온다. 754 : NewTransactionsByPriceAndNonce creates a transaction set that can retrieve price sorted transactions in a nonce-honouring way. (가격과 논스를 기준으로 트랜잭션들을 정렬) 755 : commitTransactions() 을 실행하고 실행이 문제 없이 끝나면 false가 나기 때문에 return이 되지 않는다. true가 나는 경우는 w.current 가 nil일 경우를 제외하고는 없다. 759 : 다음 슬라이드에 자세히
miner/worker.go | func commitTransactions() 576~579 : 현재 남은 가스리밋이 21000보다 작으면 반복문 종료 581~584 : 처리할 다음 tx를 갖고온다. tx 더 갖고올게 없으면 반복문 종료 589~597 : signer 관련 설정 599 : stateDB Prepare() 601 : commitTransaction호출
miner/worker.go | func commitTransaction() 실제 트랜잭션을 처리하는 과정 앞서 반복문에서 계속 트랜잭션을 하나씩 가져오면서 실행을 하게 된다. 트랜잭션을 실행하기 전에 스냅샷을 찍어뒀다가 트랜잭션 실행 과정에 에러가 나게되면 이전 스냅샷으로 revert한다. 실행을 정상적으로 마치면 트랜잭션과 리십트를 w.current.txs / receipts 슬라이스에 각각 append한다.
core/state_processor.go | func ApplyTransaction() 트랜잭션 실행의 자세한 내용은 아래 링크 참고 https://docs.google.com/presentation/d/1UE4mMz7395pZmVOhFecnNv33AN0sGaNsmyr0hB2uNDs/edit#slide=id.p
miner/worker.go | func commitNewWork() commitTransactions() 의 실행을 마치면 w.commit()이 실행된다.
miner/worker.go | func commit() 앞에서 commitTransactions 로 트랜잭션들을 처리하고 나면 commit을 실행하게 된다. commit은 블록 생성의 마감 작업에 들어가게 된다. 772 : consensus engine의 Finalize() 호출 781 : w.taskCh 로 task를 보낸다.
ethash/consensus.go | func Finalize() 548 : 블록 보상 합치는 로직 549 : 블록 헤더의 stateRoot에 IntermediateRoot 할당 552 : 블록 생성
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
miner/worker.go | func taskLoop() 397 : w.taskCh에서 받아온 값을 task에 할당한다. 받아온 값은 receipts, state, block, createdAt(time.Now()) 403 : 고루틴으로 w.seal()을 실행한다.
miner/worker.go | func seal() 367 : 블록을 Sealing하는 메소드인 w.engine.Seal()을 실행한다. Seal()은 PoW에서 유명한 논스를 찾는 과정이다. 378: 이후 이를 바탕으로 받은 리턴값을 t.block에 할당하고 이를 w.resultCh에 전송한다.
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
miner/worker.go | func resultLoop() 416 : w.resultCh에서 값을 result로 받아옴. 420 : result.block 을 따로 꺼내어 변수 block에 할당 432~436 : 블록과 state를 데이터베이스에 커밋한다. 439 : NewMinedBlockEvent를 Post한다. => minedBroadcastLoop()가 실행된다. 454 : unconfirmed 블록에 새 블록을 insert한다.
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
miner/worker.go | func newWorkLoop() 352~355 : 마이닝 사이클이 한번 돌고 난 뒤 다시 마이닝을 시작하는 부분
요약 정리 Geth Node …. Ethereum service Shh service EthStats Service Dashboard service EthStats Service Txpool Miner Blockchain Protocol Manager ….
worker.startCh 채널로 신호 전달 2) 노드 실행 과정 1) 서비스 생성 과정 startNode() miner.new() startMining() miner.newWorker() miner.Start() go newWorkLoop() go mainLoop() go taskLoop() go resultLoop() 새로운 마이닝 작업을 전달 worker.startCh 채널로 신호 전달 생성된 블록을 db에 commit / 다른 노드에게 전파 / 새로운 마이닝 시작 신호 트랜잭션 실행 등 블록 생성의 대부분을 담당 컨센서스엔진(PoW or PoA)에 따라 Sealing (ex. nonce 값 찾기)
Thank you