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, defaultdefaultNumWorkersFor 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 required
  • queryHashesPerSec: 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 work
  • updateHashes: This is useless, ignore
  • newBlockCh: 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 code
  • started: records whether Miner started
  • discreteMining: this is not assigned in the current code, alwaysfalseI think it should be deleted. Have to mentionissue #961
  • wgandworkerWg: all related to controlling the Goroutine process
  • speedMonitorQuit: It doesn’t help, ignore
  • quit: 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:

  1. The first code starts the mining routine with the specified number of workers
  2. The second place is to monitor the number of workers that should be kept and increase or decrease
  3. 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:

  1. The first passmining.NewBlockTemplateA block is generated from the template
  2. The second is in a violent manner (from0Start counting one by one) for the right to account for the block
  3. The third is throughchain.ProcessBlock(block)Try adding it to a block chain held locally
  4. The fourth place is the directionnewBlockChThe 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:

  1. The first is to find the parent block specified by the newly generated block from the local blockchain and calculate it from itseedHow is it calculated? We don’t care about it for the moment, just know that it is used to check the workload. Right
  2. 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 untilmaxNonceThe end.maxNonceIt’s a very large number^uint64(0)(i.e.2^64 - 1) is basically impossible to traverse in a single block time.
  3. 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 isquitThere 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.
  4. The fourth place is to treat the current loop number asNonceTo compute the Hash value
  5. The fifth place is the invocationdifficulty.CheckProofOfWorkTo 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.