![main_logo](https://steemitimages.com/DQmWD8NAiZnbdwXrZArK2knyXvrxeQBDqFtrsWErvTXgnZ5/main_logo.jpg)
이 글은 자바 개발자의 go-ethereum(geth 클라이언트) 소스 읽기 시리즈 Part2 연재 중 두 번째 글입니다.
1. [Day 01: 마이닝 과정 실습](https://steemit.com/kr/@woojin.joe/geth-part2-day01)
2. **(본 글)** Day 02: 마이닝 시작 과정 코드 읽기 (1)
3. Day 03: 마이닝 시작 과정 코드 읽기 (2)
4. TBD
전체 연재 목록은 아래 페이지에서 확인해 주세요.
http://www.notforme.kr/block-chain/geth-code-reading
## Part2 연재의 대상 독자 및 목표
이 글은 독자 분들이 적어도 Java와 같은 OOP 계열의 언어로 프로그래밍 경험이 있다는 것을 가정합니다. 또한 계정, 채굴 등 블록체인과 이더리움과 관련된 기초적인 개념과 이더리움 백서나 황서의 내용을 간단하게 알고 있다고 가정합니다. `geth` 코드를 읽으면서 백서 및 황서에서 정의된 스펙이 어떻게 구현되었는지를 확인하는 것이 목적입니다.
더불어 이 연재는 다음 3가지 목적을 염두하고 쓴 것입니다.
1. 새로운 언어(`Go`)를 오픈소스 코드를 읽으며 배운다.
2. 오픈소스를 읽으며 코드리딩 능력을 배양한다.
3. 블록체인의 기술을 직접 코드를 통해서 익힌다.
지난 Part1에서는 총 6번의 글을 통해서 `geth`구조와 실행방법에 대해서 살펴봤습니다. Part2에서는 좀 더 구체적으로 이더리움의 핵심 기능들이 어떻게 구현되었는지 Part1에서와 마찬가지로 코드를 통해서 알아보려고 합니다
## 다루는 내용
지난 글에서는 로컬에 하나의 `geth` 노드를 띄운 후 마이너를 실행하고 블록이 생성되는 과정을 직접 실습했습니다. 이 과정에서 발생한 이더리움을 다른 신규계정으로 전송하는 것 또한 실습했습니다.
오늘은 이더리움을 마이닝하는 과정을 실습이 아닌 코드로 따라가 보려고 합니다. 물론 마이닝 과정 가운데 일부 로직은 좀 더 세부적으로 살펴볼 것도 있습니다. 예를 들면 `PoW`를 실행하는 `ethash`나 블록상태를 저장하는 로직 등이 그렇습니다. 오늘은 이러한 세부 로직을 모두 파악하기 보다는 **코드 레벨에서 지난 번 실습한 마이닝 동작과정을 큰 흐름으로 살펴보는 것**이 목적입니다.
> 한 번에 많은 내용을 살펴보고 글로 담기가 쉽지 않을 것 같습니다. 앞으로도 되도록 너무 길지 않게 분석할 코드의 내용을 논리적으로 잘개 쪼개서 포스팅을 하려고 합니다.
마지막으로 오늘부터 본격적으로 시작하는 Part2에서 읽는 코드의 커밋 해시 [0fe479e](https://github.com/ethereum/go-ethereum/tree/0fe47e98c4d93f952dc7415f401fd03b2664a21d)입니다.
---
## 마이닝 시작 포인트
첫 출발은 마이닝을 실행하는 지점에서 출발하려고 합니다. `geth`에서 마이너를 실행시키는 방법은 2가지가 있습니다. 하나는 `geth` 실행 시에 `--mine`옵션을 주는 것이고 다른 하나는 지난 시간에 했던 방법처럼 `web3`를 사용하여 `rpc`로 실행하는 방법입니다.
> 마이너를 실행시키는 2가지 방법
>
> 1. `geth` 실행 시 mine 옵션 주기
> 2. `web3`를 활용하여 `miner.start()` 명령을 콘솔에 입력하기
2가지 방법은 호출 방식만 다를 뿐 결국 `Ethereum` 객체의 필드인 `miner`의 `Start` 함수를 호출합니다. 지난 글 마지막에 콘솔에서 `miner.start()`를 호출할 때 실행되는 함수에 디버깅 포인트를 걸어서 `GoLand`에서 실행된 `geth`의 동작 흐름을 멈추게 했었습니다. 이 지점이 바로 `miner`의 `Start` 함수였습니다.
그럼 `miner` 객체의 코드에서부터 시작해야 겠네요!
### 마이너의 생성 로직
`miner`관련 코드를 읽기에 앞서 `miner` 객체의 생성 로직을 살펴보고 넘어갈까 합니다. Part1 [마지막 글](https://steemit.com/kr/@woojin.joe/go-ethereum-day-6)에서 `geth`의 노드 실행 과정 중에서 `Ethereum` 객체의 생성과정을 이미 살펴봤는데 이 때 `miner` 생성된 것을 기억하시나요? 복습 차원에서 [코드](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/eth/backend.go#L169-L170)를 다시 가져와 봅니다.
```go
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
eth.miner.SetExtra(makeExtraData(config.ExtraData))
```
\
지난 글에서는 `miner.New()`가 호출 된다는 것까지 확인을 했었습니다. 오늘은 `New` 함수 코드를 들여다 보겠습니다.
```go
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine) *Miner {
miner := &Miner{
eth: eth,
mux: mux,
engine: engine,
worker: newWorker(config, engine, common.Address{}, eth, mux),
canStart: 1,
}
miner.Register(NewCpuAgent(eth.BlockChain(), engine))
go miner.update()
return miner
}
```
\
함수에서 인자로 넘겨 받은 내용은 다음과 같습니다.
1. `eth` 객체: eth의 속성인 `miner` 에게 스스로의 참조를 전달하고 있네요.
2. `config`: 타입이름을볼 때 블록체인 관련 설정
3. `mux`: 이벤트 처리를 할 때 쓸 것으로 추정
4. `engine`: 마이닝을 위해서는 합의 과정이 관련이 있겠지요?
이렇게 넘겨받은 인자로 `Miner` 타입의 객체를 생성합니다. 이 때 `worker`라는 속성은 `newWorker` 함수로 생성이 되는 것을 볼 수 있습니다. 그리고 나서 `Register` 함수를 `NewCpuAgent` 함수의 반환 값으로 호출하고 있네요. `Register` 함수는 간단합니다.
```go
func (self *Miner) Register(agent Agent) {
if self.Mining() {
agent.Start()
}
self.worker.register(agent)
}
```
\
인자로 `Agent`를 받네요. 앞에서 `NewCpuAgent`가 `Agent`타입의 객체를 반환한다는 것을 유추할 수 있겠네요. 코드의 로직은 간단합니다. 이미 마이닝 중이면 `agent`의 `Start`를 실행하고, 아니면 `worker`에 인자로 받은 `agent`를 등록합니다. 아하! 실제 등록되는 위치는 `miner`가 아니라 `worker`군요!
그럼 이제`worker`를 생성한 `newWorker` [함수](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/miner/worker.go#L135-L164)를 봐야할 것 같습니다.
```go
func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
worker := &worker{
config: config,
engine: engine,
eth: eth,
mux: mux,
txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
chainDb: eth.ChainDb(),
recv: make(chan *Result, resultQueueSize),
chain: eth.BlockChain(),
proc: eth.BlockChain().Validator(),
possibleUncles: make(map[common.Hash]*types.Block),
coinbase: coinbase,
agents: make(map[Agent]struct{}),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
}
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
go worker.update()
go worker.wait()
worker.commitNewWork()
return worker
}
```
\
`worker`가 많은 속성을 갖고 있네요! 이쯤 되면 `miner` 객체는 마이닝의 실행/종료를 위한 컨테이너 정도의 역할이고 로직의 많은 부분은 `worker`가 담당한다는 것을 짐작할 수 있습니다. 속성 중에 자세히 들여다 보면 `agents`가 맵으로 선언되어 있네요. 앞에서 `Register`할 때 `worker`에 했던 부분이 여기서 연결되네요.
객체를 생성한 후에는 여러 이벤트를 구독하기 위한 채널 설정이 있습니다. 친절하게 주석으로 설명해주는 것이 보이시죠? 트랜잭션 Pool에 추가된 새 트랜잭션을 구독하는 이벤트와 블록체인의 변경 상태를 구독하는 채널을 셋팅합니다. 이어서 `go` 문법을 사용하여 `worker.update()`와 `worker.wait()` 루틴을 각각 실행하고 `commitNewWork` 함수를 실행한 뒤에 생성된 `worker`를 반환합니다.
> 계속해서 앞으로 코드에서 Work라는 단어가 나올텐데요. 체인에 포함되지 않은 블록을 마이닝하여 포함시키는 과정. 즉 우리가 개념적으로 하는 그 작업 증명이 코드레벨에서는 Work, Worker와 연관된 것이라 보면 될 것 같습니다.
`worker`를 반환하기 전에 고루틴으로 호출한 `update`, `wait`은 for 문을 계속 돌면서 특정 이벤트에 따라 실행하는 로직입니다. 우선은 이 정도까지만 확인하고 이 부분은 이후에 다시 확인하는 것으로 하겠습니다.
마지막 `worker.commitNewWork()`이 실제 작업증명을 위해 컨펌되지 않은 블록을 포함시키는 부분인데요. 이 함수는 앞으로 보게될 마이너를 시작하는 함수에서 다시 호출될 겁니다. 여기서는 로직상 호출은 되지만 내부에서 마이닝 실행중이 아니라 실제 블럭을 커밋하지 않습니다. `newWorker`에서 왜 이 함수를 호출하는지는 아직 정확하게는 모르겠네요;...
자 다시 `miner`의 생성 로직인 `New` 함수로 돌아오면 `miner` 객체를 생성한 후 이를 반환하기 직전에 고루틴으로 다음과 같은 함수를 실행합니다.
```go
go miner.update()
```
\
이 함수는 마이닝 하기 전에 기존에 네트워크에 있는 블록체인 정보를 받아와서 싱크하는 로직입니다. 들어가 보면 전체 블록 정보를 받기 전까지는 마이닝을 하지 않고 모두 받고 나서 시작하는 로직이 구현되어 있습니다. 이 함수의 구현은 궁금한 분들을 위해서 [링크](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/miner/miner.go#L78-L104)로 대체합니다.
### 마이너의 시작
앞에서 마이너를 시작하는 방법은 `geth` 실행 시 옵션으로 실행하는 방법과 콘솔에서 명령을 주는 방법이 있다고 했습니다. 간단히 각 코드 부분이 어디인지 확인해 보겠습니다.
#### geth 실행 시 마이너 시작부분
이 코드는 `geth`패키지 `main.go` 파일의 `startNode` 함수에 있습니다. Part1에서 `utils.StartNode` 함수만 설명하고 이후 로직은 자세하게 다루지 않았는데요. 이 함수의 마지막 부분을 보면 다음과 같은 [로직](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/cmd/geth/main.go#L283-L306)이 있습니다.
```go
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
// 마이닝을 실행하기 위한 조건 확인 하는 부분은 생략....
// Set the gas price to the limits from the CLI and start mining
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
if err := ethereum.StartMining(true); err != nil {
utils.Fatalf("Failed to start mining: %v", err)
}
}
```
\
보시는 바와 같이 if문의 조건으로 `utils.MiningEnabledFlag.Name`을 볼 수 있습니다. 이 플래그가 바로 터미널에서 옵션으로 주는 `--mine` 입니다. 마이닝 옵션이 있으면 아래 `ethereum.StartMining` 함수를 호출하는 것을 확인할 수 있습니다. 이 함수가 실제 마이닝을 시작하는 함수가 됩니다. 그럼 콘솔에서 `rpc` 호출은 어떻게 되는지 보겠습니다.
#### rpc로 실행 시 마이너 시작부분
`geth`는 `rpc` 엔드포인드를 관리하는 로직이 `rpc` 패키지에 있고 실제 각 API는 관련 패키지의 `api.go`에 선언되어 있습니다. 하지만 마이닝을 시작하는 엔드포인트는 `miner`패키지는 아닙니다. 실제로는 `eth` 패키지의 `api.go` 에 있습니다. `Ethereum` 객체가 하나의 컨테이너로서 관련된 로직을 위임하고 있는 구조라 실제 API엔드포인트가 여기 있는 것 같습니다. [코드](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/eth/api.go#L135-L160)를 그럼 한 번 볼까요?
```go
// Start the miner with the given number of threads. If threads is nil the number
// of workers started is equal to the number of logical CPUs that are usable by
// this process. If mining is already running, this method adjust the number of
// threads allowed to use.
func (api *PrivateMinerAPI) Start(threads *int) error {
// Set the number of threads if the seal engine supports it
if threads == nil {
threads = new(int)
} else if *threads == 0 {
*threads = -1 // Disable the miner from within
}
type threaded interface {
SetThreads(threads int)
}
if th, ok := api.e.engine.(threaded); ok {
log.Info("Updated mining threads", "threads", *threads)
th.SetThreads(*threads)
}
// Start the miner and return
if !api.e.IsMining() {
// Propagate the initial price point to the transaction pool
api.e.lock.RLock()
price := api.e.gasPrice
api.e.lock.RUnlock()
api.e.txPool.SetGasPrice(price)
return api.e.StartMining(true)
}
return nil
}
```
\
친절하게 주석에 API 설명이 있네요. 함수 초반 몇가지 로직을 일단 지나치고 보면 마지막 `return` 부분에 `StartMining` 함수가 보이네요. 바로 이전에 `geth` 실행 시 호출 했던 것과 동일한 함수입니다!
### StartMining 함수
이 함수는 마이너를 실행을 감싸고 있습니다. [코드](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/eth/backend.go#L335-L358)는 다음과 같습니다.
```go
func (s *Ethereum) StartMining(local bool) error {
eb, err := s.Etherbase()
if err != nil {
log.Error("Cannot start mining without etherbase", "err", err)
return fmt.Errorf("etherbase missing: %v", err)
}
if clique, ok := s.engine.(*clique.Clique); ok {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error("Etherbase account unavailable locally", "err", err)
return fmt.Errorf("signer missing: %v", err)
}
clique.Authorize(eb, wallet.SignHash)
}
if local {
// If local (CPU) mining is started, we can disable the transaction rejection
// mechanism introduced to speed sync times. CPU mining on mainnet is ludicrous
// so none will ever hit this path, whereas marking sync done on CPU mining
// will ensure that private networks work in single miner mode too.
atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
}
go s.miner.Start(eb)
return nil
}
```
\
첫 줄의 코드는 마이닝시 생성된 이더리움을 전달할 코인베이스 계정을 가져오는 함수입니다. 함수 구현을 보면 특별한 예외가 없는한 `geth`가 실행되는 곳의 지갑 첫번째 계정을 반환합니다. 지난 시간 실습 때 아무런 계정 생성없이 `miner.start()`하면 에러가 반환되어 계정 생성 했던 부분이 바로 이 코드와 관련이 있습니다.
마이닝을 실행하는 실제 로직은 함수의 제일 마지막 `return`문 위에 있는 고루틴 `s.miner.Start`함수입니다. 코인베이스를 인자로 전달하는 이 함수가 마이너를 시작하는 지점입니다.
#### Miner의 Start 함수
이 함수가 바로 지난 시간 디버깅 포인트를 걸었던 부분입니다. 전체 [코드](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/miner/miner.go#L106-L119)를 한 번 살펴볼까요?
```go
func (self *Miner) Start(coinbase common.Address) {
atomic.StoreInt32(&self.shouldStart, 1)
self.SetEtherbase(coinbase)
if atomic.LoadInt32(&self.canStart) == 0 {
log.Info("Network syncing, will start miner afterwards")
return
}
atomic.StoreInt32(&self.mining, 1)
log.Info("Starting mining operation")
self.worker.start()
self.worker.commitNewWork()
}
```
\
인자로 받은 코인 베이스를 마이너 객체에 저장하고 마이닝 과정을 시작해도 되는지 `canStart` 변수로 확인하고 있습니다. 이 변수는 최초 생성 시에는 `1`로 셋팅되어 있기 때문에 위 조건으로 실패할 일은 없습니다. 다만 앞에서 마이너 객체를 생성할 때 기존의 블록체인을 받아오는 함수 `miner.update()` 에서 값이 변경될 수도 있습니다. 이 경우는 `update` 할 내용이 있어서 모든 데이터를 받아오면 그때 `canStart`를 `1`로 변경한 후 `Start`를 실행합니다.
다음으로 시작할 준비가 되면 `mining` 변수에 `1`을 셋팅합니다. 이후에도 코드 여기저기에서 `self.mining`의 상태를 보고 마이닝 중인지 확인합니다. 이어서 `start` 함수와 `commitNewWork` 함수를 각각 호출합니다.
##### worker.start 함수
앞에서 마이너 객체를 생성할 때 `Register` 함수에서도 봤지만 마이너 객체는 실제 마이닝 작업을 하는 객체가 아닙니다. 이 함수의 [코드](https://github.com/ethereum/go-ethereum/blob/6ce21a4744461fc35c05b1c5fcc92136761be747/miner/worker.go#L204-L214)를 보면 실제 실행은 `agent`임을 확인할 수 있습니다.
```go
func (self *worker) start() {
self.mu.Lock()
defer self.mu.Unlock()
atomic.StoreInt32(&self.mining, 1)
// spin up agents
for agent := range self.agents {
agent.Start()
}
}
```
\
최종적으로 위 코드를 통해서 마이닝 작업을 수행하는 함수는 `agent` 라는 것을 알 수 있습니다. 그렇다면 `self.agents`에는 어떤 `agent`가 들어있을까요? 우선 `miner.New()`함수에서 `CpuAgent`가 등록했다는 것을 떠올려보면 최소한 하나의 `agent`는 들어 있다는 것을 보장할 수 있습니다. 추가로 `rpc`로 마이닝을 실행할 경우에도 `RemoteAgent` 타입이 추가됩니다. 따라서 최대 2개의 `agent`가 실행됩니다.
depth가 깊어지네요… `CpuAgent`와 `RemoteAgent`의 각각 `Start`함수를 살펴보기 전에 여기서 한번 정리를 해볼까요.
1. `geth` 실행 시점이든, `rpc` 호출이든 => `Ethereum`객체의 `StartMining`함수를 호출
2. `StartMining`함수는 => `Miner` 객체의 `Start` 함수 호출
3. `Miner` 객체의 `Start` 함수는 => `Worker` 객체의 `Start` 함수 호출
4. `Worker`객체의 `Start`함수는 => 각 `Agent`의 `Start` 함수 호출
이제 각 `Agent`의 함수를 살펴봅시다. 먼저 `RemoteAgent` 함수의 [코드](https://github.com/ethereum/go-ethereum/blob/fbf57d53e272c2d79d4d899bb94db824678de2d5/miner/remote_agent.go#L80-L87)부터 보겠습니다.
```go
func (a *RemoteAgent) Start() {
if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
return
}
a.quitCh = make(chan struct{})
a.workCh = make(chan *Work, 1)
go a.loop(a.workCh, a.quitCh)
}
```
\
2개의 채널을 생성한 후 이 값을 `loop` 함수에 인자로 전달하면서 고루틴으로 실행하고 있네요.`loop` 함수의 코드를 보면 인자로 전달 받은 채널에 값이 있을 때에 따른 처리로직만 있을 뿐 실제 마이닝 관련 코드는 없습니다. 함수에 선언된 주석을 보면 모니터링 관련 코드라고 하는 군요. 따라서 `loop`의 [코드](https://github.com/ethereum/go-ethereum/blob/fbf57d53e272c2d79d4d899bb94db824678de2d5/miner/remote_agent.go#L165-L202)를 굳이 자세히 들여다 보지 않겠습니다.
이제 우리의 관심사는 `CpuAgent` 입니다. 이 함수의 코드는 아주 간단합니다.
```go
func (self *CpuAgent) Start() {
if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) {
return // agent already started
}
go self.update()
}
```
\
이 함수는 현재 마이닝이 실행중인지 확인 후 고루틴으로 `update`함수를 실행하고 있네요. 아… 마이닝 로직이 한 depth 더 들어가는 군요. `update` 함수까지 [코드](https://github.com/ethereum/go-ethereum/blob/fbf57d53e272c2d79d4d899bb94db824678de2d5/miner/agent.go#L78-L100)를 들어가 봅시다.
```go
func (self *CpuAgent) update() {
out:
for {
select {
case work := <-self.workCh:
self.mu.Lock()
if self.quitCurrentOp != nil {
close(self.quitCurrentOp)
}
self.quitCurrentOp = make(chan struct{})
go self.mine(work, self.quitCurrentOp)
self.mu.Unlock()
case <-self.stop:
self.mu.Lock()
if self.quitCurrentOp != nil {
close(self.quitCurrentOp)
self.quitCurrentOp = nil
}
self.mu.Unlock()
break out
}
}
}
```
\
보이시나요? 코드 한 가운데 고루틴으로 `mine` 함수를 호출하는 것이!!! 자 이제 이 함수가 어떻게 `mine`을 호출하는지를 알아야겠지요? 이 코드는 사실 `golang`의 채널과 `goto`문의 독특한 문법으로 돌고 있는 코드입니다. 설명하자면 이렇습니다. 우선 `for`문은 계속 돕니다. 이 `for` 문 안에서 `self.workCh` 채널과 `self.stop` 채널에 데이터가 들어오기를 기다립니다. 이 중에 `workCh`을 통해서 블록체인에 포함할 작업이 `work`로 들어올때 바로 `self.mine()` 함수까지 실행이 되는 것입니다.
반면 `stop` 채널로 데이터가 들어오면 `break out`으로 인해 `for`문을 탈출합니다. 기존의 C 언어를 공부한 분이라면 `goto`문이 가리키는 곳이 for문 위라서 다시 `for` 문이 실행되는 것으로 오해할 수 있는데요.(제가 그랬습니다.) `golang`은 해당 레이블로 돌아간 뒤에 돌고 있던 루프문의 다음 문장으로 이동한다고 합니다! 앞으로도 여기저기서 위와 같이 채널과 `goto`문을 활용하여 특정 이벤트나 데이터를 수신하면서 처리하는 로직을 보게 될 겁니다. 지금 눈에 잘 익혀 두세요 ^^
자 그럼! 여기서 자연스럽게 질문이 하나 나와야 합니다. 바로 **누가 `workCh`에 데이터를 밀어 넣느냐 하는 것**입니다. 이건 바로 위에서 `worker.start`함수 다음으로 실행 한 `worker.commitNewWork()`함수의 역할입니다. 오늘 글에서 이 부분까지 다루면 좋겠지만 이미 적지 않은 내용을 다룬 것 같아… 여기서 한번 호흡을 가다듬고 마이닝을 위한 나머지 코드는 다음 글에서 다뤄야 할 것 같습니다.
---
## 결론
오늘은 지난 시간에 실습했던 마이닝 과정을 코드로 차근히 따라가보는 일을 했습니다. 오늘 살펴본 코드는 위에서 한번 정리했지만 다시 정리하면 다음과 같은 흐름이었습니다.
> 1. geth` 실행 시점이든, `rpc` 호출이든 => `Ethereum`객체의 `StartMining`함수를 호출
> 2. `StartMining`함수는 => `Miner` 객체의 `Start` 함수 호출
> 3. `Miner` 객체의 `Start` 함수는 => `Worker` 객체의 `Start` 함수 호출
> 4. `Worker`객체의 `Start`함수는 => 각 `Agent`의 `Start` 함수 호출
> 5. `Agent` 가운데 `CpuAgent` 함수가 바로 마이닝 관련 함수이고 여기서 => `update` 함수 호출
> 6. `update`함수 안에서 `workCh`을 수신하다가 => 데이터가 들어오면 `mine` 함수 호출
긴 여정이었습니다. 다음 글에서 오늘 미쳐 다루지 못한 `mine`함수와 함께 `worker.commitNewWork` 함수에서 어떻게 `workCh` 채널에 `Work` 객체를 넣는지 살펴보고자 합니다.
그럼 다음 연재에서 뵙겠습니다.