Author: freewind
Biyuan Project warehouse:
Making address: https://github.com/Bytom/bytom
Gitee address: https://gitee.com/BytomBlockchain/bytom
When we set up a standalone node with byTom init –chain_id=solonet for local testing, we’ll soon find ourselves with an embarrassing problem: zero balance. Even if we use bytom node – mining, open mining, because we are a single state in theory, the machine work force is cut in force, should be able to dig into every time, but don’t know why, when I was trying to find always dig, so going to study simple compared to the original mining process and see what is there any way can GaiDian, Dig more BTM for yourself to facilitate the subsequent test.
So today I’m going to do a source code analysis of biogen’s mining process, but considering that it’s going to get to the core of biogen, I’m going to skip the parts that are too complicated and explore them thoroughly when the time is right.
If we do a quick search, we can see that there is a type called CPUMiner in the original code, and we should be able to circle around it.
Let’s start with the original startup and see how CPUMiner is started.
The following entry function for byTom node –mining:
cmd/bytomd/main.go#L54-L57
func main(a) {
cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
cmd.Execute()
}
Copy the code
Since the node argument is passed in, create node and start it:
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 {
// ...
}
Copy the code
When a Node object is created, the CPUMiner object is also created:
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
}
Copy the code
Here you can see the creation of two mining-related objects, NewCPUMiner and miningPool. Let’s look at the code for NewCPUMiner:
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,
}
}
Copy the code
As you can see from the fields here, CPUMiner is at work:
- Three external objects that may be needed are:
chain
(represents the blockchain held by the machine),accountManager
(Management account),txPool
(Trading pool) numWorkers
: Should keep several workers mining, defaultdefaultNumWorkers
For constant1
, that is, there is only one worker by default. This is a bit of a loss for multi-core cpus, but if you want to mine it, you can make it larger and have the same number of cores (though it’s unlikely to be found on a regular computer).updateNumWorkers
: If the outside world wants to change the number of workers, it can do so by sending messages to this channel. CPUMiner listens on it and adds or subtracts workers as requiredqueryHashesPerSec
: This is useless, ignore it. I’ve found that developers in Belgium like to design up front, and there’s a lot of code that doesn’t workupdateHashes
: This is useless, ignorenewBlockCh
: a channel from the outside to tell the outside world that it successfully dug a block and put it into the local blockchain, so that other places can use it (e.g. broadcast out)
However, not all of CPUMiner’s fields appear here, just a few that need to be initialized specifically. The full one is 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
}
Copy the code
You can see a few more:
sync.Mutex
: Provides locks for CPUMiner to synchronize between different Goroutine codestarted
: records whether Miner starteddiscreteMining
: this is not assigned in the current code, alwaysfalse
I think it should be deleted. Have to mentionissue #961wg
andworkerWg
: all related to controlling the Goroutine processspeedMonitorQuit
: It doesn’t help, ignorequit
: The outside world can send a message to this channel to tell CPUMiner to quit
Go back to n.start to see when cpuMiner is started:
node/node.go#L169-L180
func (n *Node) OnStart(a) error {
if n.miningEnable {
n.cpuMiner.Start()
}
// ...
}
Copy the code
Since we passed the argument mining, n.miningEnable is true, and n.cpuminer. Start will run:
mining/cpuminer/cpuminer.go#L188-L205
func (m *CPUMiner) Start(a) {
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")}Copy the code
There’s not much to be said for this code, which basically guarantees that m.started won’t start twice, and then puts the real work in m.MiningWorkerController () :
mining/cpuminer/cpuminer.go#L126-L125
func (m *CPUMiner) miningWorkerController(a) {
/ / 1.
var runningWorkers []chan struct{}
launchWorkers := func(numWorkers uint64) {
for i := uint64(0); i < numWorkers; i++ {
quit := make(chan struct{})
runningWorkers = append(runningWorkers, quit)
m.workerWg.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 == numRunning {
continue
}
if m.numWorkers > numRunning {
launchWorkers(m.numWorkers - numRunning)
continue
}
for i := numRunning - 1; i >= m.numWorkers; i-- {
close(runningWorkers[i])
runningWorkers[i] = nil
runningWorkers = runningWorkers[:i]
}
/ / 3.
case <-m.quit:
for _, quit := range runningWorkers {
close(quit)
}
break out
}
}
m.workerWg.Wait()
close(m.speedMonitorQuit)
m.wg.Done()
}
Copy the code
This method looks like a lot of code, but it actually does three things:
- The first code starts the mining routine with the specified number of workers
- The second place is to monitor the number of workers that should be kept and increase or decrease
- The third is safely closed when it is known to be closed
The code is pretty clear, so I don’t think I need to say more.
As you can see from code 1, the actual mining is done inside 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()
}
Copy the code
Some of the less important code is omitted from the method. We can see what’s going on in several places in the annotations:
- The first pass
mining.NewBlockTemplate
A block is generated from the template - The second is in a violent manner (from
0
Start counting one by one) for the right to account for the block - The third is through
chain.ProcessBlock(block)
Try adding it to a block chain held locally - The fourth place is the direction
newBlockCh
The channel sends a message informing the outside world that it has dug a new block
mining.NewBlockTemplate
NewBlockTemplate (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
}
Copy the code
It’s a long method, but I’ve ignored it because it’s too detailed, and it gets to the heart of the original, so it’s ok to get to the bottom of it for now.
In a Block Block, there is some basic information, such as the hash value of the previous Block, the mining difficulty value, the timestamp, etc., the main body has various transaction records, and the hash summary of multiple layers. In this approach, the main logic is to find this information and wrap it up as a Block object for later processing. I don’t think it’s very useful to look at these details before we have a deep understanding of the original blockchain structure and rules, so it’s easy to ignore them and come back to them later when it’s appropriate.
m.solveBlock
Moving on, when a Block object is generated by NewBlockTemplate, it is handed to the solveBlock method:
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
}
Copy the code
This approach is the part of mining we care about the most: fighting for the right to keep an account.
I’ve broken the code into four pieces and explained them briefly:
- The first is to find the parent block specified by the newly generated block from the local blockchain and calculate it from it
seed
How is it calculated? We don’t care about it for the moment, just know that it is used to check the workload. Right - The second is the use of force to calculate the target value, which is used to fight for the right to account. Why violence? Since the mining algorithm guarantees that there’s no faster way to solve a puzzle than to compute it one by one from zero, so here we try it one by one from zero until
maxNonce
The end.maxNonce
It’s a very large number^uint64(0)
(i.e.2^64 - 1
) is basically impossible to traverse in a single block time. - The third point is to look to see if you need to exit each time you do the calculation in the loop. There are two ways to get out. One is
quit
There is a new message in the channel, and someone reminds you to quit (maybe it is time); Another is that the local blockchain has received a new block, and the height is higher than their own, indicating that someone else has grabbed it. - The fourth place is to treat the current loop number as
Nonce
To compute the Hash value - The fifth place is the invocation
difficulty.CheckProofOfWork
To check whether the current hash value satisfies the current difficulty. If satisfied, it means that they have the right of accounting, this block is valid; Otherwise, keep counting
Then we could look at the five 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
}
Copy the code
In this method, you can see the appearance of a tensority.AIHash, which is more friendly than the original ai workload algorithm. If you are interested, you can go and have a look. Since this algorithm is certainly more difficult than expected in this article, it will not be explored. In the future, if there are opportunities and conditions, maybe I will try to understand (don’t expect ~)
As can be seen from this method, it calls the relevant method in tensority.AIHash to determine whether the current hash meets the difficulty requirement.
At the beginning of this article, we said that we wanted to find a way to modify the original code so that in Solonet mode, we could mine normally and get BTM for testing. When I saw this method, I thought I had it, we just need to change it to always return true:
func CheckProofOfWork(hash, seed *bc.Hash, bits uint64) bool {
compareHash := tensority.AIHash.Hash(hash, seed)
return HashToBig(compareHash).Cmp(CompactToBig(bits)) <= 0 || true
}
Copy the code
Here may let a person feel a bit strange, why in the last place with | | true, rather than direct return true in front? This is because if you return true, you may have a problem with the timestamp check in the program.
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
Copy the code
The reason for this is unclear, but it may be that the original code takes some time, just enough to pass the check. Returning true is too fast and will not pass the check. But I have a feeling that there is a problem here, which will be studied later.
After this modification, recompile and start the node than the original, every block can be dug, almost a block per second (suddenly become a multimillionate)
m.chain.ProcessBlock
We should now go back to the third point in the generateBlocks method, which is:
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()
}
Copy the code
ProcessBlock adds the block that has just been successfully credited to the local blockchain:
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
}
Copy the code
You can see that this is actually throwing the work out because it puts the block to be processed into the Chain. ProcessBlockCh channel, along with the reply channel. Then listen for messages such as reply.
So who will handle the content in C. rocessBlockCh? Of course, it’s by Chain, but this is the original core, so we’ll leave it for later, so we’ll skip it today.
If nothing goes wrong, we go to block 4 and put the hash of this block in the newBlockCh channel. This newBlockCh comes in from the outside and is used in many places. When there is new data in it, the local machine has mined a new block (and added it to the local blockchain), which can be used elsewhere for other operations (such as broadcasting out).
So here, we solved today’s problem, leaving a lot of holes to fill in later.