当前位置:网站首页>Packaging and uplink of btcd transaction process (III)

Packaging and uplink of btcd transaction process (III)

2022-06-13 00:16:00 Ethan97

Overview of transaction packaging and chain

btcd First, get the transaction from the memory pool , Fill blocks with transactions , And fill in the necessary information on the block . Subsequent block POW Calculation . After calculating the block hash matching the difficulty value ,btcd Perform the final step of verification for the new block , Connect the new block to the local main chain , And broadcast the new block to the peer .

BitCoin RPCs

To understand how transactions are packaged into blocks , Last published , First of all, from the Bitcoin API Reference as well as btcd API Reference To view related RPC call .

getblocktemplate

getblocktemplate ( "template_request" )

Its description is as follows :

If the request parameters include a ‘mode’ key, that is used to explicitly select between the default ‘template’ request or a ‘proposal’.
It returns data needed to construct a block to work on.

getblocktemplate RPC The data needed to construct a block will be returned .

generateblock

generateblock "output" ["rawtx/txid",...]

Its description is as follows :

Mine a block with a set of ordered transactions immediately to a specified address or descriptor (before the RPC call returns)

generateblock RPC Mining with a given ordered transaction , The beneficiary's address needs to be specified .

submitblock

submitblock "hexdata" ( "dummy" )

Its description is as follows :

Attempts to submit new block to network.

Try to submit a new block to the network .

setgenerate

Set the server to generate coins (mine) or not.
NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the --miningaddr option to provide which payment addresses to pay created blocks to for this RPC to function.

setgenerate Set up server Whether to start mining . If open , The server will use CPU dig . Need to use –miningaddr Specify beneficiary's address .

mining Overall process analysis

With these three RPC, We can roughly determine the process from packaging transactions to publishing blocks .
First, through getblocktemplate RPC Get the information you need to assemble a block , And then through generateblock RPC Generate a block , Finally through submitblock RPC Publish blocks to the network .
It can also be done through setgenerate RPC Set the server on mining, Complete the generation of blocks on the server 、 dig 、 Submit .
Select below getblocktemplate RPC and setgenerate RPC Analyze .

getblocktemplate

from Client issue RPC Start . The RPC be located rpcclient/mining.go, The code is as follows :

// GetBlockTemplate returns a new block template for mining.
func (c *Client) GetBlockTemplate(req *btcjson.TemplateRequest) (*btcjson.GetBlockTemplateResult, error) {
    
	return c.GetBlockTemplateAsync(req).Receive()
}

It actually calls the asynchronous version GetBlockTemplateAsync, And call it Receive Method blocks waiting for the result to return :

// GetBlockTemplateAsync returns an instance of a type that can be used to get the
// result of the RPC at some future time by invoking the Receive function on the
// returned instance.
//
// See GetBlockTemplate for the blocking version and more details.
func (c *Client) GetBlockTemplateAsync(req *btcjson.TemplateRequest) FutureGetBlockTemplateResponse {
    
	cmd := btcjson.NewGetBlockTemplateCmd(req)
	return c.SendCmd(cmd)
}

Called Client Of SendCmd Method :

// SendCmd sends the passed command to the associated server and returns a
// response channel on which the reply will be delivered at some point in the
// future. It handles both websocket and HTTP POST mode depending on the
// configuration of the client.
func (c *Client) SendCmd(cmd interface{
    }) chan *Response {
    
	...
	responseChan := make(chan *Response, 1)
	jReq := &jsonRequest{
    
		id:             id,
		method:         method,
		cmd:            cmd,
		marshalledJSON: marshalledJSON,
		responseChan:   responseChan,
	}

	c.sendRequest(jReq)

	return responseChan
}

This method declares a capacity of 1 The channel used for reply , Describe the caller GetBlockTemplateAsync Will not block waiting for the method to return results .
SendCmd Called further Client Of sendRequest Method , After that, we use it POST Method to send the request to the server .

On the server ,RPC The processing method of is located in rpcserver.go, The treatment is handleGetBlockTemplate, The code is as follows :

// handleGetBlockTemplate implements the getblocktemplate command.
func handleGetBlockTemplate(s *rpcServer, cmd interface{
    }, closeChan <-chan struct{
    }) (interface{
    }, error) {
    
	c := cmd.(*btcjson.GetBlockTemplateCmd)
	request := c.Request

	// Set the default mode and override it if supplied.
	mode := "template"
	if request != nil && request.Mode != "" {
    
		mode = request.Mode
	}

	switch mode {
    
	case "template":
		return handleGetBlockTemplateRequest(s, request, closeChan)
	case "proposal":
		return handleGetBlockTemplateProposal(s, request)
	}

	return nil, &btcjson.RPCError{
    
		Code:    btcjson.ErrRPCInvalidParameter,
		Message: "Invalid mode",
	}
}

This method first determines the requested mode mode What is it? , The default is template. With template For example , It calls for handleGetBlockTemplateRequest Method , The code is as follows :

func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateRequest, closeChan <-chan struct{
    }) (interface{
    }, error) {
    
	...

	// Get and return a block template. A new block template will be
	// generated when the current best block has changed or the transactions
	// in the memory pool have been updated and it has been at least five
	// seconds since the last template was generated. Otherwise, the
	// timestamp for the existing block template is updated (and possibly
	// the difficulty on testnet per the consesus rules).
	if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil {
    
		return nil, err
	}
	return state.blockTemplateResult(useCoinbaseValue, nil)
}

This method calls blockTemplateResult Method . Check this method to see blockTemplateResult Returns the current and state Associated block template .
First of all to see state What is it? , Its structure is defined as follows :

// gbtWorkState houses state that is used in between multiple RPC invocations to
// getblocktemplate.
type gbtWorkState struct {
    
	sync.Mutex
	lastTxUpdate  time.Time
	lastGenerated time.Time
	prevHash      *chainhash.Hash
	minTimestamp  time.Time
	template      *mining.BlockTemplate
	notifyMap     map[chainhash.Hash]map[int64]chan struct{
    }
	timeSource    blockchain.MedianTimeSource
}

gbtWorkState Many of them are preserved getblocktemplate RPC The state between calls . among BlockTemplate The domain holds the block template we need . So this template *mining.BlockTemplate When was it set up ? Actually in handleGetBlockTemplateRequest in , Called updateBlockTemplate Method sets this template

func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error {
    
		
		blkTemplate, err := generator.NewBlockTemplate(payAddr)
		...
		template = blkTemplate
		...
		state.template = template
		...
}

NewBlockTemplate Method actually creates a new block template . This block template uses transactions from the memory pool to create blocks . among , Incoming payToAddress Used to create coinbase transaction .

NewBlockTemplate Choose a trading strategy

The transactions selected and included are prioritized according to several factors :

  1. Each transaction has a value based 、 Enter the priority of time and size calculation . By a larger amount 、 Transactions consisting of older inputs and smaller sizes have the highest priority ;
  2. Calculate the cost per kilobyte per transaction . Transactions with higher cost per kilobyte are preferred .
  3. Take into account all policy settings related to block generation .

Transactions that use only the output of other transactions already in the block chain will be immediately added to the priority queue , The queue is based on priority ( Then there is the cost per kilobyte ) Or the cost per kilobyte ( Then there is the priority ) Prioritization depends on BlockPrioritySize Policy setting whether to allocate space for high priority transactions . Transactions that use the output of other transactions in the source pool will be added to the dependency mapping , So that after including the transactions on which they depend , You can add them to the priority queue . Once the high priority area ( If configured ) Transactions filled , Or the priority is lower than the high priority , The priority queue will be updated to cost per kilobyte ( Then there is the priority ) Prioritize . When the cost per kilobyte is less than TxMinFreeFee When setting policy , The transaction will be skipped , Unless BlockMinSize Policy set to non-zero , under these circumstances , The block will be populated at a low cost / Free trade , Until the block size reaches the minimum size . Skipping anything will cause the block to exceed BlockMaxSize Policy settings 、 Transactions that exceed the maximum number of signature operations allowed per block or otherwise invalidate the block .

thus ,NewBlockTemplate Method returns a new block template :

	return &BlockTemplate{
    
		Block:             &msgBlock,
		Fees:              txFees,
		SigOpCosts:        txSigOpCosts,
		Height:            nextBlockHeight,
		ValidPayAddress:   payToAddress != nil,
		WitnessCommitment: witnessCommitment,
	}, nil

setgenerate

It was said that ,setgenerate Set up server Whether to start mining . If open , The server will use CPU dig . Need to use –miningaddr Specify beneficiary's address .
First, check the processing setgennerate Methods handleSetGenerate

// handleSetGenerate implements the setgenerate command.
func handleSetGenerate(s *rpcServer, cmd interface{
    }, closeChan <-chan struct{
    }) (interface{
    }, error) {
    
		...
		// It's safe to call start even if it's already started.
		s.cfg.CPUMiner.SetNumWorkers(int32(genProcLimit))
		s.cfg.CPUMiner.Start()
		...
}

handleSetGenerate First set up the mining worker Number , And then call CPUMiner Of Start Methods start mining . Keep looking at Start Method :

// Start begins the CPU mining process as well as the speed monitor used to
// track hashing metrics. Calling this function when the CPU miner has
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (m *CPUMiner) Start() {
    
	m.Lock()
	defer m.Unlock()

	// Nothing to do if the miner is already running or if running in
	// discrete mode (using GenerateNBlocks).
	if m.started || m.discreteMining {
    
		return
	}

	m.quit = make(chan struct{
    })
	m.speedMonitorQuit = make(chan struct{
    })
	m.wg.Add(2)
	go m.speedMonitor()
	go m.miningWorkerController()

	m.started = true
	log.Infof("CPU miner started")
}

Start Method enables a speed monitor speedMonitor Coroutine and a worker thread controller miningWorkerController coroutines .
Here's the main thing miningWorkerController The implementation of the :

// miningWorkerController launches the worker goroutines that are used to
// generate block templates and solve them. It also provides the ability to
// dynamically adjust the number of running worker goroutines.
func (m *CPUMiner) miningWorkerController() {
    
	// launchWorkers groups common code to launch a specified number of
	// workers for generating blocks.
	var runningWorkers []chan struct{
    }
	launchWorkers := func(numWorkers uint32) {
    
		for i := uint32(0); i < numWorkers; i++ {
    
			quit := make(chan struct{
    })
			runningWorkers = append(runningWorkers, quit)

			m.workerWg.Add(1)
			go m.generateBlocks(quit)
		}
	}

	// Launch the current number of workers by default.
	runningWorkers = make([]chan struct{
    }, 0, m.numWorkers)
	launchWorkers(m.numWorkers)

out:
	for {
    
		select {
    
		// Update the number of running workers.
		case <-m.updateNumWorkers:
			// No change.
			numRunning := uint32(len(runningWorkers))
			if m.numWorkers == numRunning {
    
				continue
			}

			// Add new workers.
			if m.numWorkers > numRunning {
    
				launchWorkers(m.numWorkers - numRunning)
				continue
			}

			// Signal the most recently created goroutines to exit.
			for i := numRunning - 1; i >= m.numWorkers; i-- {
    
				close(runningWorkers[i])
				runningWorkers[i] = nil
				runningWorkers = runningWorkers[:i]
			}

		case <-m.quit:
			for _, quit := range runningWorkers {
    
				close(quit)
			}
			break out
		}
	}

	// Wait until all workers shut down to stop the speed monitor since
	// they rely on being able to send updates to it.
	m.workerWg.Wait()
	close(m.speedMonitorQuit)
	m.wg.Done()
}


stay for In circulation , adopt go m.generateBlocks(quit) Open all worker coroutines . And then miningWorkerController Monitor mining worker Have the numbers changed , Yes, mining worker Adjust the number , And listen for exit messages , And then quit mining .

generateBlocks It's called worker, Keep looking at generateBlocks The method is how to mine :

// generateBlocks is a worker that is controlled by the miningWorkerController.
// It is self contained in that it creates block templates and attempts to solve
// them while detecting when it is performing stale work and reacting
// accordingly by generating a new block template. When a block is solved, it
// is submitted.
//
// It must be run as a goroutine.
func (m *CPUMiner) generateBlocks(quit chan struct{
    }) {
    
	log.Tracef("Starting generate blocks worker")

	// Start a ticker which is used to signal checks for stale work and
	// updates to the speed monitor.
	ticker := time.NewTicker(time.Second * hashUpdateSecs)
	defer ticker.Stop()
out:
	for {
    
		// Quit when the miner is stopped.
		select {
    
		case <-quit:
			break out
		default:
			// Non-blocking select to fall through
		}

		// Wait until there is a connection to at least one other peer
		// since there is no way to relay a found block or receive
		// transactions to work on when there are no connected peers.
		if m.cfg.ConnectedCount() == 0 {
    
			time.Sleep(time.Second)
			continue
		}

		// No point in searching for a solution before the chain is
		// synced. Also, grab the same lock as used for block
		// submission, since the current block will be changing and
		// this would otherwise end up building a new block template on
		// a block that is in the process of becoming stale.
		m.submitBlockLock.Lock()
		curHeight := m.g.BestSnapshot().Height
		if curHeight != 0 && !m.cfg.IsCurrent() {
    
			m.submitBlockLock.Unlock()
			time.Sleep(time.Second)
			continue
		}

		// Choose a payment address at random.
		rand.Seed(time.Now().UnixNano())
		payToAddr := m.cfg.MiningAddrs[rand.Intn(len(m.cfg.MiningAddrs))]

		// Create a new block template using the available transactions
		// in the memory pool as a source of transactions to potentially
		// include in the block.
		template, err := m.g.NewBlockTemplate(payToAddr)
		m.submitBlockLock.Unlock()
		if err != nil {
    
			errStr := fmt.Sprintf("Failed to create new block "+
				"template: %v", err)
			log.Errorf(errStr)
			continue
		}

		// Attempt to solve the block. The function will exit early
		// with false when conditions that trigger a stale block, so
		// a new block template can be generated. When the return is
		// true a solution was found, so submit the solved block.
		if m.solveBlock(template.Block, curHeight+1, ticker, quit) {
    
			block := btcutil.NewBlock(template.Block)
			m.submitBlock(block)
		}
	}

	m.workerWg.Done()
	log.Tracef("Generate blocks worker done")
}

generateBlocks Did these things :

  1. monitor quit passageway , If the channel is closed , be worker sign out ;
  2. If there is no connected peer , Is blocked for one second ;
  3. Check whether the main chain has been synchronized , Otherwise, it will block for one second and wait for the main chain synchronization to complete ;
  4. Choose one of the beneficiary's designated addresses ;
  5. Call the preceding NewBlockTemplate Get a block template ;
  6. call solveBlock To solve the problem of nonce value ;
  7. Successfully find qualified nonce after , call NewBlock Assemble blocks , call submitBlock Submit block .

solveBlock Is really doing POW The place of , The code is as follows :

// solveBlock attempts to find some combination of a nonce, extra nonce, and
// current timestamp which makes the passed block hash to a value less than the
// target difficulty. The timestamp is updated periodically and the passed
// block is modified with all tweaks during this process. This means that
// when the function returns true, the block is ready for submission.
//
// This function will return early with false when conditions that trigger a
// stale block such as a new block showing up or periodically when there are
// new transactions and enough time has elapsed without finding a solution.
func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
	ticker *time.Ticker, quit chan struct{
    }) bool {
    

	// Choose a random extra nonce offset for this block template and
	// worker.
	enOffset, err := wire.RandomUint64()
	if err != nil {
    
		log.Errorf("Unexpected error while generating random "+
			"extra nonce offset: %v", err)
		enOffset = 0
	}

	// Create some convenience variables.
	header := &msgBlock.Header
	targetDifficulty := blockchain.CompactToBig(header.Bits)

	// Initial state.
	lastGenerated := time.Now()
	lastTxUpdate := m.g.TxSource().LastUpdated()
	hashesCompleted := uint64(0)

	// Note that the entire extra nonce range is iterated and the offset is
	// added relying on the fact that overflow will wrap around 0 as
	// provided by the Go spec.
	for extraNonce := uint64(0); extraNonce < maxExtraNonce; extraNonce++ {
    
		// Update the extra nonce in the block template with the
		// new value by regenerating the coinbase script and
		// setting the merkle root to the new value.
		m.g.UpdateExtraNonce(msgBlock, blockHeight, extraNonce+enOffset)

		// Search through the entire nonce range for a solution while
		// periodically checking for early quit and stale block
		// conditions along with updates to the speed monitor.
		for i := uint32(0); i <= maxNonce; i++ {
    
			select {
    
			case <-quit:
				return false

			case <-ticker.C:
				m.updateHashes <- hashesCompleted
				hashesCompleted = 0

				// The current block is stale if the best block
				// has changed.
				best := m.g.BestSnapshot()
				if !header.PrevBlock.IsEqual(&best.Hash) {
    
					return false
				}

				// The current block is stale if the memory pool
				// has been updated since the block template was
				// generated and it has been at least one
				// minute.
				if lastTxUpdate != m.g.TxSource().LastUpdated() &&
					time.Now().After(lastGenerated.Add(time.Minute)) {
    

					return false
				}

				m.g.UpdateBlockTime(msgBlock)

			default:
				// Non-blocking select to fall through
			}

			// Update the nonce and hash the block header. Each
			// hash is actually a double sha256 (two hashes), so
			// increment the number of hashes completed for each
			// attempt accordingly.
			header.Nonce = i
			hash := header.BlockHash()
			hashesCompleted += 2

			// The block is solved when the new block hash is less
			// than the target difficulty. Yay!
			if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
    
				m.updateHashes <- hashesCompleted
				return true
			}
		}
	}

	return false
}

solveBlock Trying to set nonce,extra nonce and timestamp To make the hash of a given block smaller than the target difficulty value . When a new block appears 、 Or when there is a new transaction and a certain period of time has passed , This method returns early .
solveBlock First, set the loop in the outer layer extraNonce, Each loop pair extraNonce Add one more ; Set the loop in the inner layer nonce, Each loop pair nonce Add one more ; Inside the loop ,solveBlock Be able to receive exit messages , And it will periodically check whether new blocks appear ; If a new transaction occurs , And a minute has passed , The exit ;
hash := header.BlockHash() The block header is hashed . If the hash meets the requirements , Will hashesCompleted Send to m.updateHashes, And back to true.( take hashesCompleted Send to m.updateHashes This is to allow the speed monitor to monitor the hash number per second )

go back to generateBlocks, Successfully find qualified nonce, extra nonce, timestamp After combination ,solveBlock return true, caller generateBlocks Continue to call NewBlock Yes template.Block Call after simply encapsulating the block submitBlock Submit the block .

submitBlock The code is as follows :

// submitBlock submits the passed block to network after ensuring it passes all
// of the consensus validation rules.
func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
    
	m.submitBlockLock.Lock()
	defer m.submitBlockLock.Unlock()

	// Ensure the block is not stale since a new block could have shown up
	// while the solution was being found. Typically that condition is
	// detected and all work on the stale block is halted to start work on
	// a new block, but the check only happens periodically, so it is
	// possible a block was found and submitted in between.
	msgBlock := block.MsgBlock()
	if !msgBlock.Header.PrevBlock.IsEqual(&m.g.BestSnapshot().Hash) {
    
		log.Debugf("Block submitted via CPU miner with previous "+
			"block %s is stale", msgBlock.Header.PrevBlock)
		return false
	}

	// Process this block using the same rules as blocks coming from other
	// nodes. This will in turn relay it to the network like normal.
	isOrphan, err := m.cfg.ProcessBlock(block, blockchain.BFNone)
	if err != nil {
    
		// Anything other than a rule violation is an unexpected error,
		// so log that error as an internal error.
		if _, ok := err.(blockchain.RuleError); !ok {
    
			log.Errorf("Unexpected error while processing "+
				"block submitted via CPU miner: %v", err)
			return false
		}

		log.Debugf("Block submitted via CPU miner rejected: %v", err)
		return false
	}
	if isOrphan {
    
		log.Debugf("Block submitted via CPU miner is an orphan")
		return false
	}

	// The block was accepted.
	coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0]
	log.Infof("Block submitted via CPU miner accepted (hash %s, "+
		"amount %v)", block.Hash(), btcutil.Amount(coinbaseTx.Value))
	return true
}

submitBlock Finally, check whether the newly excavated block has expired ( By comparing the hash of the last block of the newly excavated block with the latest block hash of the current main chain ).
adopt ProcessBlock Method , Broadcast blocks to peers , This ProcessBlock Method by cpuminer Medium Config Structs hold as method variables , In fact, this method variable is assigned SyncManager Of ProcessBlock Method , The code is as follows :

// ProcessBlock makes use of ProcessBlock on an internal instance of a block
// chain.
func (sm *SyncManager) ProcessBlock(block *btcutil.Block, flags blockchain.BehaviorFlags) (bool, error) {
    
	reply := make(chan processBlockResponse, 1)
	sm.msgChan <- processBlockMsg{
    block: block, flags: flags, reply: reply}
	response := <-reply
	return response.isOrphan, response.err
}

It will processBlockMsg Send to msgChan passageway , Then try to start from processBlockMsg The attached reply channel reads the reply message , And return it to the caller . At this time, I returned to the familiar blockHandler Method ( It was mentioned in the section of submitting the transaction that ), receive processBlockMsg The code for is as follows :

out:
	for {
    
		select {
    
		case m := <-sm.msgChan:
			switch msg := m.(type) {
    
			...
			case processBlockMsg:
				_, isOrphan, err := sm.chain.ProcessBlock(
					msg.block, msg.flags)
				if err != nil {
    
					msg.reply <- processBlockResponse{
    
						isOrphan: false,
						err:      err,
					}
				}

				msg.reply <- processBlockResponse{
    
					isOrphan: isOrphan,
					err:      nil,
				}
			...
		...
		}
	}


When from msgChan The message received was processBlockMsg Type , call ProcessBlock Method , And return the response .
BlockChain Object's ProcessBlock Method is the last step of blockchain . It includes blockExists Reject duplicate blocks 、checkBlockSanity Ensure that the block complies with all rules 、addOrphanBlock Orphan treatment 、maybeAcceptBlock Insert blocks into the blockchain .

maybeAcceptBlock Basically, a block is placed on the blockchain , It will return whether a block is finally on the main chain .

  1. It will perform several validation checks depending on its location ( The height of the new block is the height of the previous block referenced plus one , Whether the difficulty meets the requirements, etc …), Before trying to add it to the main chain ;
  2. Save the block to the database ;
  3. Create a blockNode object , And index it ;
  4. connectBestChain Connect blocks to the main chain .

connectBestChain Connect blocks to the main chain . Typically , Just connect the new block to the main chain . However, it may extend a side chain , It can become the main chain , It may not be the main chain .
connectBestChain Database that updates the block index ;
Perform several checks again to verify that the block can be connected to the main chain ;
The main concern is when to update UTXO:

		if fastAdd {
    
			err := view.fetchInputUtxos(b.db, block)
			if err != nil {
    
				return false, err
			}
			err = view.connectTransactions(block, &stxos)
			if err != nil {
    
				return false, err
			}
		}

The transactions in the block inputs Contains UTXOs Bring it here , And the transaction in the block outputs As new UTXOs.
call connectBlock Connect blocks to the main chain . stay connectBlock There are the following codes in :

err := b.indexManager.ConnectBlock(dbTx, block, stxos)

ConnectBlock Performed a series of updates , Finally, the new block is connected to the main chain .
Although the new block is connected to the main chain , But this is only a local blockchain , Other nodes in the network do not know that a new block has been generated . Generally speaking , A node connects a new block , As many nodes as possible should be notified as soon as possible , Otherwise, other nodes may also find qualified nonce, Also connected to a new block , Then the block connected by the current node may become a side chain . Let's go back to maybeAcceptBlock Method :

When the new block is successfully connected to the main chain , The following code follows :

	// Notify the caller that the new block was accepted into the block
	// chain. The caller would typically want to react by relaying the
	// inventory to other peers.
	b.chainLock.Unlock()
	b.sendNotification(NTBlockAccepted, block)
	b.chainLock.Lock()

maybeAcceptBlock By calling sendNotification Notify the caller , The new block has been accepted . The code is as follows :

// sendNotification sends a notification with the passed type and data if the
// caller requested notifications by providing a callback function in the call
// to New.
func (b *BlockChain) sendNotification(typ NotificationType, data interface{
    }) {
    
	// Generate and send the notification.
	n := Notification{
    Type: typ, Data: data}
	b.notificationsLock.RLock()
	for _, callback := range b.notifications {
    
		callback(&n)
	}
	b.notificationsLock.RUnlock()
}


This method first constructs a Notification, Then traverse BlockChain Object's notifications, Constructed with Notification As a parameter callback notifications Every function in , among ,Subscribe Method can subscribe to this notification :

// Subscribe to block chain notifications. Registers a callback to be executed
// when various events take place. See the documentation on Notification and
// NotificationType for details on the types and contents of notifications.
func (b *BlockChain) Subscribe(callback NotificationCallback) {
    
	b.notificationsLock.Lock()
	b.notifications = append(b.notifications, callback)
	b.notificationsLock.Unlock()
}

Check out the Usage You know ,RPCServer and SyncManager All subscribed to this message , And pass in their respective handleBlockchainNotification Method as a callback function .
To look at first rpcServer Of handleBlockchainNotification Corresponding branch :

case blockchain.NTBlockAccepted:
		block, ok := notification.Data.(*btcutil.Block)
		if !ok {
    
			rpcsLog.Warnf("Chain accepted notification is not a block.")
			break
		}

		// Allow any clients performing long polling via the
		// getblocktemplate RPC to be notified when the new block causes
		// their old block template to become stale.
		s.gbtWorkState.NotifyBlockConnected(block.Hash())

When new blocks expire their old block templates , Through NotifyBlockConnected Method pair “ adopt getblocktemplate RPC perform long polling The client of ” Notice .

In the view SyncManager Of handleBlockchainNotification Corresponding branch :

	// A block has been accepted into the block chain. Relay it to other
	// peers.
	case blockchain.NTBlockAccepted:
		// Don't relay if we are not current. Other peers that are
		// current should already know about it.
		if !sm.current() {
    
			return
		}

		block, ok := notification.Data.(*btcutil.Block)
		if !ok {
    
			log.Warnf("Chain accepted notification is not a block.")
			break
		}

		// Generate the inventory vector and relay it.
		iv := wire.NewInvVect(wire.InvTypeBlock, block.Hash())
		sm.peerNotifier.RelayInventory(iv, block.MsgBlock().Header)

Here, the new block is passed to other peers . Generate a Inventory vector And pass it on .

// RelayInventory relays the passed inventory vector to all connected peers
// that are not already known to have it.
func (s *server) RelayInventory(invVect *wire.InvVect, data interface{
    }) {
    
	s.relayInv <- relayMsg{
    invVect: invVect, data: data}
}

RelayInventory The inventory vector is passed to server Of relayInv.
This passage is server Of peerHandler Read :

		// New inventory to potentially be relayed to other peers.
		case invMsg := <-s.relayInv:
			s.handleRelayInvMsg(state, invMsg)

handleRelayInvMsg Implemented a closure :

state.forAllPeers(func(sp *serverPeer) {
    
		if !sp.Connected() {
    
			return
		}

		// If the inventory is a block and the peer prefers headers,
		// generate and send a headers message instead of an inventory
		// message.
		if msg.invVect.Type == wire.InvTypeBlock && sp.WantsHeaders() {
    
			blockHeader, ok := msg.data.(wire.BlockHeader)
			if !ok {
    
				peerLog.Warnf("Underlying data for headers" +
					" is not a block header")
				return
			}
			msgHeaders := wire.NewMsgHeaders()
			if err := msgHeaders.AddBlockHeader(&blockHeader); err != nil {
    
				peerLog.Errorf("Failed to add block"+
					" header: %v", err)
				return
			}
			sp.QueueMessage(msgHeaders, nil)
			return
		}
		...
	})

Finally, the encapsulated information is put into the peer sending queue .QueueMessage The code is as follows :

// QueueMessage adds the passed bitcoin message to the peer send queue.
//
// This function is safe for concurrent access.
func (p *Peer) QueueMessage(msg wire.Message, doneChan chan<- struct{
    }) {
    
	p.QueueMessageWithEncoding(msg, doneChan, wire.BaseEncoding)
}

Finally, step by step , Leave it to infrastruture The code in the package completes sending the new block to all known peers .

thus , Completed the source code analysis from the creation of blocks to the transfer of blocks to peers . The peer receives the new block , It will be verified , And decide whether to connect it to the main chain . Whether the block can finally be linked , It depends on whether most nodes in the network finally accept it . If there are new blocks excavated by other nodes in the network at the same time , The success or failure of the uplink will continue to be decided by the next block of the new block .

原网站

版权声明
本文为[Ethan97]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202280602093315.html