Author: freewind
Compared to the original project warehouse:
GitHub Address: Https://github.com/Bytom/bytom
Gitee Address: Https://gitee.com/BytomBlockc ...
When we bytom init --chain_id=solonet set up a single node for local testing, we soon find ourselves facing an embarrassing problem: the balance is 0. Even if we use bytom node --mining open mining, theoretically because we are stand-alone state, the machine is the whole network calculation force, should be able to dig every time, but do not know why, when I try to find always dug, so the intention is to simply study the mining process than the original, to see if there is no way to pity Dorado what, Give yourself a single machine to dig more BTM to facilitate the subsequent test.
So today I'm going to go through the source code to analyze the ore-digging process, but given that it's definitely going to involve a bit more than the original core, it's too complicated for me to skip, and those places will be thoroughly researched when the time is ripe.
If we search quickly, we can find that there is a type in the original code called CPUMiner , we surround it should be.
First, start with the original boot and see CPUMiner how it's started.
The following is bytom node --mining the corresponding entry function:
Cmd/bytomd/main.go#l54-l57
func main() { cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir())) cmd.Execute()}
Because the parameters node are passed in, the node is created and started:
Cmd/bytomd/commands/run_node.go#l41-l54
func runNode(cmd *cobra.Command, args []string) error { // Create & start node n := node.NewNode(config) if _, err := n.Start(); err != nil { // ...}
When you create a node object, you also create the CPUMiner object:
node/node.go#l59-l142
func NewNode(config *cfg.Config) *Node { // ... node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh) node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh) // ... return node}
Here you can see the creation of two things related to mining, one is NewCPUMiner , the other is miningPool . Let's look at NewCPUMiner the corresponding code first:
mining/cpuminer/cpuminer.go#l282-l293
func NewCPUMiner(c *protocol.Chain, accountManager *account.Manager, txPool *protocol.TxPool, newBlockCh chan *bc.Hash) *CPUMiner { return &CPUMiner{ chain: c, accountManager: accountManager, txPool: txPool, numWorkers: defaultNumWorkers, updateNumWorkers: make(chan struct{}), queryHashesPerSec: make(chan float64), updateHashes: make(chan uint64), newBlockCh: newBlockCh, }}
From the fields here you can see that cpuminer at work:
- The three objects that may be used externally are:
chain (representing the blockchain held by the machine), ( accountManager management account), txPool (Trading pool)
numWorkers: Several workers should be kept in the mining, the default value defaultNumWorkers is constant 1 , that is, there is only one worker by default. It's a bit of a loss for multicore CPUs, so you can make it bigger with the same number of cores (but it's not likely to be dug up with a normal computer).
updateNumWorkers: If the outside world wants to change the number of workers, it can be implemented by sending messages to this channel. Cpuminer will monitor it and increase or decrease the worker as required.
queryHashesPerSec: It's useless, ignore it. I found that developers like the original design, there are a lot of such useless code
updateHashes: This is useless, ignore
newBlockCh: An external channel used to tell the outside of the success of digging a block, and has been placed in the local blockchain, other places can use it (such as broadcast)
However CPUMiner , not all of the fields that appear here are just a few that need to be deliberately initialized. Complete here:
Mining/cpuminer/cpuminer.go#l29-l45
type CPUMiner struct { sync.Mutex chain *protocol.Chain accountManager *account.Manager txPool *protocol.TxPool numWorkers uint64 started bool discreteMining bool wg sync.WaitGroup workerWg sync.WaitGroup updateNumWorkers chan struct{} queryHashesPerSec chan float64 updateHashes chan uint64 speedMonitorQuit chan struct{} quit chan struct{} newBlockCh chan *bc.Hash}
You can see a few more out there:
sync.Mutex: Provides lock for Cpuminer, facilitates synchronization in different Goroutine code
started: Records whether the miner is started
discreteMining: This is not assigned a value in the current code, always false , I think it should be deleted. Issue has been raised #961
wgAnd workerWg : Both are related to the control of the goroutine process.
speedMonitorQuit: It's no use, ignoring
quit: The outside world can send messages to this channel to notify Cpuminer to exit
Go back and n.Start see cpuMiner when it started:
node/node.go#l169-l180
func (n *Node) OnStart() error { if n.miningEnable { n.cpuMiner.Start() } // ...}
Since we passed in the parameter --mining , so n.miningEnable yes, it true n.cpuMiner.Start will run:
mining/cpuminer/cpuminer.go#l188-l205
func (m *CPUMiner) Start() { m.Lock() defer m.Unlock() if m.started || m.discreteMining { return } m.quit = make(chan struct{}) m.speedMonitorQuit = make(chan struct{}) m.wg.Add(1) go m.miningWorkerController() m.started = true log.Infof("CPU miner started")}
This code is not much to say, mainly by judging the m.started guarantee will not repeat the start, and then put the real work in the m.miningWorkerController() middle:
mining/cpuminer/cpuminer.go#l126-l125
Func (M *cpuminer) Miningworkercontroller () {//1. var runningworkers []chan struct{} launchworkers: = Func (Numworkers uint64) {for I: = UInt64 (0); I < numwork ERs i++ {quit: = Make (chan struct{}) runningworkers = append (Runningworkers, quit) m.workerw G.add (1) Go M.generateblocks (Quit)}} runningworkers = make ([]chan struct{}, 0, m.numworkers) Launchworkers (m.numworkers) out:for {select {//2. Case <-m.updatenumworkers:numrunning: = UInt64 (len (runningworkers)) if m.numworkers = = Numrunnin G {Continue} if M.numworkers > numrunning {launchworkers (m.numwo rkers-numrunning) Continue} for I: = numRunning-1; I >= m.numworkers; i--{Close (Runningworkers[i]) runningworkers[i] = Nil Runningworkers = Runni ngworkers[:I]}//3. Case <-m.quit:for _, Quit: = Range Runningworkers {close (quit)} Brea K Out}} m.workerwg.wait () Close (m.speedmonitorquit) M.wg.done ()}
This method seems to be a lot of code, but the fact that things are still better to clarify, mainly to do three things:
- The 1th code is to start the mining routines according to the number of worker specified
- The 2nd place is to monitor the number of workers to be kept and increase or decrease
- The 3rd is closed safely when it is known to be closed.
The code is relatively clear and should not be spoken more.
As you can see in the 1th code, the real mining work is put in generateBlocks :
mining/cpuminer/cpuminer.go#l84-l119
func (m *CPUMiner) generateBlocks(quit chan struct{}) { ticker := time.NewTicker(time.Second * hashUpdateSecs) defer ticker.Stop()out: for { select { case <-quit: break out default: } // 1. block, err := mining.NewBlockTemplate(m.chain, m.txPool, m.accountManager) // ... // 2. if m.solveBlock(block, ticker, quit) { // 3. if isOrphan, err := m.chain.ProcessBlock(block); err == nil { // ... // 4. blockHash := block.Hash() m.newBlockCh <- &blockHash // ... } } } m.workerWg.Done()}
The method omits some of the less important code, and we can look at what we are doing from a few places in the callout:
- The 1th place
mining.NewBlockTemplate creates a block from a template
- The 2nd place is to
0 compete for the right to account for the block by means of violence (counting from the beginning)
- The 3rd place is to
chain.ProcessBlock(block) try to add it to the local block chain.
- The 4th place is to send a message to the
newBlockCh channel to inform the outside world that they have dug up a new block.
Mining. Newblocktemplate
Let's take a look at the 1th place mining.NewBlockTemplate :
mining/mining.go#l67-l154
func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager) (b *types.Block, err error) { // ... return b, err}
This method is very long, but the content is ignored by me, because its content is too detailed, and has touched the core of the original, so now about to know about it.
Compared to a block block, there are some basic information, such as the first piece of hash value in its head, mining difficulty, time stamp and so on, the main department has a variety of transactions, as well as multiple layers of hash summary. In this approach, the main logic is to find the information and then wrap it up in a block object, which is then processed by the back. I feel that in the case where we have not yet understood the structure and rules of the blockchain, it is not very useful to look at things that are too detailed, so it is easy to ignore them and look back when they are appropriate.
M.solveblock
We continue down, and when NewBlockTemplate a block object is generated, it is handed to the solveBlock method processing:
Mining/cpuminer/cpuminer.go#l50-l75
func (m *CPUMiner) solveBlock(block *types.Block, ticker *time.Ticker, quit chan struct{}) bool { // 1. header := &block.BlockHeader seed, err := m.chain.CalcNextSeed(&header.PreviousBlockHash) // ... // 2. for i := uint64(0); i <= maxNonce; i++ { // 3. select { case <-quit: return false case <-ticker.C: if m.chain.BestBlockHeight() >= header.Height { return false } default: } // 4. header.Nonce = i headerHash := header.Hash() // 5. if difficulty.CheckProofOfWork(&headerHash, seed, header.Bits) { return true } } return false}
This method is what we are most concerned about in mining: The Scramble for accounting rights.
I divided the code into 4 pieces, in turn, a brief explanation:
- The 1th place is to find the parent chunk specified by the newly generated chunk from the local blockchain, and it is calculated by it
seed , how it is calculated we do not care for the moment (more complex), at this time just know that it is used to check the amount of work on it.
- The 2nd is the use of violent methods to calculate the target value for the contention of accounting rights. Why is that a violent way? Because the mining algorithm ensures that you want to solve the problem, there is no faster than starting from 0, so here's a 0 start to try, until the
maxNonce end. maxNonceis a very large number ^uint64(0) (ie 2^64 - 1 ), it is basically impossible to traverse through within a chunk time.
- The 3rd point is to see if you need to exit before calculating in each loop. In both cases, you should exit, one is a
quit new message in the channel, be alerted to exit (may be time), the other is the local blockchain has received a new block, and height compared to their own high, indicating that someone else has been robbed.
- The 4th place is to
Nonce calculate the hash value as the current loop number.
- The 5th place is called
difficulty.CheckProofOfWork to check whether the currently calculated hash value satisfies the current difficulty. This block is valid if it is satisfied that it has the right of accounting, otherwise it will continue to be calculated.
Then we'll look at the 5th place difficulty.CheckProofOfWork :
Consensus/difficulty/difficulty.go#l120-l123
func CheckProofOfWork(hash, seed *bc.Hash, bits uint64) bool { compareHash := tensority.AIHash.Hash(hash, seed) return HashToBig(compareHash).Cmp(CompactToBig(bits)) <= 0}
In this method, you can see that there tensority.AIHash is one, which is more than the original AI-friendly workload algorithm, the relevant paper download address: Https://github.com/Bytom/byto ..., interested students can go to see. The difficulty of this algorithm is certainly beyond the expectations of this article, so it is not studied. In the future, if there is a chance, maybe I will try to understand (don't expect ~)
From this method can be seen, it is called tensority.AIHash in the relevant method to determine whether the current calculation of the hash to meet the difficulty requirements.
At the beginning of this article, we said we would like to find a way to modify the code of the original, so that we solonet can dig normally in the pattern and get the BTM for testing. When I see this method, I think I've found it, and we just need to change it to return it forever true :
func CheckProofOfWork(hash, seed *bc.Hash, bits uint64) bool { compareHash := tensority.AIHash.Hash(hash, seed) return HashToBig(compareHash).Cmp(CompactToBig(bits)) <= 0 || true}
It may seem a little strange here, why add in the last place, instead of || true directly back in front true ? This is because, if you return directly true , it may cause problems with the time stamp check in the program, the following error occurs:
time="2018-05-17T12:10:14+08:00" level=error msg="Miner fail on ProcessBlock block, timestamp is not in the valid range: invalid block" height=32
The reason is not yet, probably because the original code is required to consume some time, just to make the check pass. If the direct return true is too fast, but the inspection can not pass. But I feel that there is a problem here, to be studied later.
After this modification, and then recompile and start the original node, each block can be dug, almost a second a block (suddenly become a millionaire:)
M.chain.processblock
We should now return to the generateBlocks 3rd place in the method, namely:
mining/cpuminer/cpuminer.go#l84-l119
func (m *CPUMiner) generateBlocks(quit chan struct{}) { //... if m.solveBlock(block, ticker, quit) { // 3. if isOrphan, err := m.chain.ProcessBlock(block); err == nil { // ... // 4. blockHash := block.Hash() m.newBlockCh <- &blockHash // ... } } } m.workerWg.Done()}
m.chain.ProcessBlockAdd to the local blockchain the block that has just been successfully credited:
protocol/block.go#l191-l196
func (c *Chain) ProcessBlock(block *types.Block) (bool, error) { reply := make(chan processBlockResponse, 1) c.processBlockCh <- &processBlockMsg{block: block, reply: reply} response := <-reply return response.isOrphan, response.err}
You can see that this is actually the job of throwing out the work, because it put the pieces to be processed into the Chain.processBlockCh channel, and also passed a channel for the other side of the reply reply . And then listen reply to the news and so on.
So who's going to deal with c.processBlockCh the content? Of course Chain , but here is more than the original core, we stay and so on after the detailed study, today will skip.
If there is no error in processing, enter the 4th block and put the block hash in the newBlockCh channel. This newBlockCh is from the outside, many places will be used. When there is new data in it, it means that the machine has dug in a new block (and has been added to the local blockchain), and other places can use it for other operations (such as broadcasting)
So here, our problem today even solved, left a lot of pits, later specifically filled.