Yitaifang Mining Source Code: clique Algorithms

Keywords: Go snapshot JSON Database network

Chain customer, specially for developers, there are questions to answer!

This article is from Block Chain Technology Community No reprinting without permission.

clique
The official consensus algorithm of ETF is ethash algorithm, which has been analyzed in detail in the previous article.

It is based on POW consensus mechanism. Miners need to calculate nonce value, which consumes a lot of calculation to match target value.

If we continue to use ethash in alliance chain or private chain solutions, it will waste energy and POW will not exist. So there is another consensus in ETF: clique based on POA.

POA, Proof of Authority. Power proof, unlike POW workload proof, POA can directly determine that several nodes have the power to block out, and the blocks produced by these nodes will be validated as valid blocks by other nodes in the whole network.

Building private chains

You can build a private chain through the operation of this article. Observing this process, you can see that when you build the Creation Block through the puppeth tool, you will be prompted to choose which consensus way, there are two options: ethash and clique. So we can see why clique is the default choice in this article.

Source code analysis
After discussing the basic concepts, let's go deep into the source code of ETF to carefully analyze the concrete implementation of clique algorithm.

The entry still chooses the sea method, which is consistent with the previous analysis of the ethash algorithm because they are different implementations of Seal.

// By contrast, the purpose of clique's sea function is to try to create a seal ed block through local signature authentication (power signature and authentication, finding power nodes).
func (c Clique) Seal(chain consensus.ChainReader, block types.Block, stop <-chan struct{}) (*types.Block, error) {

header := block.Header()

number := header.Number.Uint64()
if number == 0 {// No Sealed Genesis Block
    return nil, errUnknownBlock
}
// Jump to the analysis of the Clique object below. Chains that do not support 0-period and refuse to seal empty blocks are not rewarded but can rotate seals.
if c.config.Period == 0 && len(block.Transactions()) == 0 {
    return nil, errWaitTransactions
}
// Do not hold the signer signer field throughout the process of sealing blocks.
c.lock.RLock() // Lock the signer and signature method in config.
signer, signFn := c.signer, c.signFn
c.lock.RUnlock()

snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)// The snapshot function is analyzed below.
// Check processing: If we sign a block without authorization
if err != nil {
    return nil, err
}
if _, authorized := snap.Signers[signer]; !authorized {
    return nil, errUnauthorized
}
// If we are a member of the Recent Signer, we will wait for the next block, //see below [underlying mechanism 3] (http:///www.cnblogs.com/Evsward/p/clique.html#%E4%EE4%B8%89%E8%E8%AE%A4%E8%AF%81%E7%BB%93%E7%E7%82%B9%E9%E7%E7%E7%E7%E7%E7%E7%E7%A%E7%E7%E7%E7%E7%E7%84%E5%E5%E5%E5%E5%E6%E6%E9 C%E9 E4%E4%E4%E4%7% AD% 89)
for seen, recent := range snap.Recents {
    if recent == signer {
        // Signer's current signer is in the latest signer. If the current block does not delete him, he can only wait.
        if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
            log.Info("Signed recently, must wait for others")
            <-stop
            return nil, nil
        }
    }
}
// Through the above checks, we are here to show that the protocol has allowed us to sign the block and wait for the work to be completed.
delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now())
if header.Difficulty.Cmp(diffNoTurn) == 0 {
    // This is not our turn to sign.
    wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime // wiggleTime = 500 * time.Millisecond // / Random Delay, allowing concurrent signatures (for each signer)
    delay += time.Duration(rand.Int63n(int64(wiggle)))

    log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
}
log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))

select {
case <-stop:
    return nil, nil
case <-time.After(delay):
}
// Core Work: Start Signing
sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())// The signFn function is shown below.
if err != nil {
    return nil, err
}
copy(header.Extra[len(header.Extra)-extraSeal:], sighash)//Replace the signature result with the Extra field of the block header (specifically for recording additional information)

return block.WithSeal(header), nil //Reassemble a block by block head

}
Analysis of Clique Objects

// Clique, a POA consensus engine, is planned to support Ethernet private test chain testnet (or build alliance or private chains yourself) after the Ropsten attack.
type Clique struct {

config *params.CliqueConfig // Consensus engine configuration parameters, see CliqueConfig source code below
db     ethdb.Database       // Database for storing and retrieving snapshot checkpoints

recents    *lru.ARCCache // Recent block snapshots to accelerate snapshot reorganization
signatures *lru.ARCCache // Signature of the nearest block to speed up mining

proposals map[common.Address]bool // The list of proposals we are currently promoting contains key-value pair mappings for address and Boolean values

signer common.Address // The signer's Ethernet address
signFn SignerFn       // Signature method to authorize hash
lock   sync.RWMutex   // Lock to protect signature fields

}
CliqueConfig source code analysis

// CliqueConfig is the configuration field of the consensus engine for POA mining.
type CliqueConfig struct {

Period uint64 `json:"period"` // The number of seconds to execute between blocks (which can be understood as the number of seconds elapsed after a block is removed from the previous block)
Epoch  uint64 `json:"epoch"`  // Epoch['i_ p_ k] Length, Reset Voting and Checkpoints

}
snapshot function analysis

// The snapshot function can take an authentication snapshot from a given point
func (c Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []types.Header) (*Snapshot, error) {

// Search for a snapshot in memory or disk to check checkpoints.
var (
    headers []*types.Header// Block header
    snap    *Snapshot// Snapshot object, see below
)
for snap == nil {
    // If you find a snapshot in memory, use the following scenario:
    if s, ok := c.recents.Get(hash); ok {
        snap = s.(*Snapshot)
        break
    }
    // If a snapshot at the disk checkpoint is found, use the following scenario:
    if number%checkpointInterval == 0 {// Checkpoint Interval = 1024 // block number, which holds a snapshot of the vote in the database.
        if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {// The loadSnapshot function is shown below.
            log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
            snap = s
            break
        }
    }
    // If we're in Genesis, take a snapshot.
    if number == 0 {
        genesis := chain.GetHeaderByNumber(0)
        if err := c.VerifyHeader(chain, genesis, false); err != nil {
            return nil, err
        }
        signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
        for i := 0; i < len(signers); i++ {
            copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:])
        }
        snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers)// The function to create a new snapshot is shown below.
        if err := snap.store(c.db); err != nil {
            return nil, err
        }
        log.Trace("Stored genesis voting snapshot to disk")
        break
    }
    // Without a snapshot of the block, the block head is collected and moved backwards.
    var header *types.Header
    if len(parents) > 0 {
        // If we have a clear parent class, force it out here.
        header = parents[len(parents)-1]
        if header.Hash() != hash || header.Number.Uint64() != number {
            return nil, consensus.ErrUnknownAncestor
        }
        parents = parents[:len(parents)-1]
    } else {
        // If no parent class is specified (or no more), go to the database
        header = chain.GetHeader(hash, number)
        if header == nil {
            return nil, consensus.ErrUnknownAncestor
        }
    }
    headers = append(headers, header)
    number, hash = number-1, header.ParentHash
}
// Find the previous snapshot, then put all pending blocks on top of it.
for i := 0; i < len(headers)/2; i++ {
    headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
}
snap, err := snap.apply(headers)//Generating a new snapshot object from the block head
if err != nil {
    return nil, err
}
c.recents.Add(snap.Hash, snap)//Store the hash of the current snapshot block in recents.

// If we generate a new checkpoint snapshot, save it to disk.
if snap.Number%checkpointInterval == 0 && len(headers) > 0 {
    if err = snap.store(c.db); err != nil {
        return nil, err
    }
    log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
}
return snap, err

}
Snapshot object source code analysis:

// Snapshot object is the state of an authentication vote at a given point
type Snapshot struct {

config   *params.CliqueConfig // configuration parameter
sigcache *lru.ARCCache        // Signature cache, the nearest block signature speeds up recovery.

Number  uint64                      `json:"number"`  // Block number created by snapshot
Hash    common.Hash                 `json:"hash"`    // Block hash created by snapshot
Signers map[common.Address]struct{} `json:"signers"` // Collection of current authenticated signers
Recents map[uint64]common.Address   `json:"recents"` // Collection of the Addresses of the Recent Signature Block
Votes   []*Vote                     `json:"votes"`   // A list of votes in chronological order.
Tally   map[common.Address]Tally    `json:"tally"`   // Avoid recalculating the current voting results.

}
Source code analysis of loadSnapshot function:

// The loadSnapshot function is used to load an existing snapshot from the database, and many of the parameter lists are keyword field attributes of the Snapshot object.
func loadSnapshot(config params.CliqueConfig, sigcache lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {

blob, err := db.Get(append([]byte("clique-"), hash[:]...))// The ethdb uses leveldb, and the open interface Dababase is shown below.
if err != nil {
    return nil, err
}
snap := new(Snapshot)
if err := json.Unmarshal(blob, snap); err != nil {
    return nil, err
}
snap.config = config
snap.sigcache = sigcache

return snap, nil

}
ethdb database open interface:

// The Database interface encapsulates all Database-related operations and all methods are thread-safe.
type Database interface {

Putter
Get(key []byte) ([]byte, error)//Get the value by a key
Has(key []byte) (bool, error)//Does a key contain valid values?
Delete(key []byte) error
Close()
NewBatch() Batch

}
New Snapshot function source code:

// The newSnapshot function creates a new snapshot by giving specific startup parameters. This method does not initialize the collection of the nearest signers, so only the Genesis Block is used.
func newSnapshot(config params.CliqueConfig, sigcache lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {

snap := &Snapshot{// That is to assemble a Snapshot object and install the corresponding parameters.
    config:   config,
    sigcache: sigcache,
    Number:   number,
    Hash:     hash,
    Signers:  make(map[common.Address]struct{}),
    Recents:  make(map[uint64]common.Address),
    Tally:    make(map[common.Address]Tally),
}
for _, signer := range signers {
    snap.Signers[signer] = struct{}{}
}
return snap

}
signFn function:

// SignerFn is a signer's callback function that requests a hash that can be generated by a background account signature.
type SignerFn func(accounts.Account, []byte) ([]byte, error)
clique Constant Configuration:

BlockPeriod = Uint64 (15) // clique specifies that the generation time of two blocks is at least 15 seconds apart, and the timestamp type.
Clique underlying mechanism
Before entering the consensus engine, the current node has generated a complete block, including block headers and seal ed transaction lists, and then entered the sea function to operate block validation through ethash or clique algorithm engine. This paper focuses on the source code analysis of clique algorithm. Based on POA consensus, clique algorithm finds several powerful "super nodes" in the nodes. Only these nodes can generate legitimate blocks, and the output blocks of other nodes will be discarded directly.

First: How does clique determine the signer and the signature method?
I searched the clique file and found a way to do this:

// The Authorize function injects a private key address (signer) and signature method signFn into the consensus engine clique to mine new blocks.
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {

c.lock.Lock()
defer c.lock.Unlock()

c.signer = signer
c.signFn = signFn

}
Then continue searching, when was the function called, and find the function StartMining in / eth/backend.go:

func (s *Ethereum) StartMining(local bool) error {

eb, err := s.Etherbase()// User address
if err != nil {
    log.Error("Cannot start mining without etherbase", "err", err)//No Ethernet account address found, error report
    return fmt.Errorf("etherbase missing: %v", err)
}
// If the clique consensus algorithm is used, the if branch is used, and if the ethash branch is used, the if is skipped.
if clique, ok := s.engine.(*clique.Clique); ok {// Comma-ok assertion grammar is analyzed below.
    wallet, err := s.accountManager.Find(accounts.Account{Address: eb})// Obtaining wallet objects from user addresses
    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)//Ad locum! The signer is injected and the signature method is obtained through the wallet object.
}
if local {
    // If local CPU mining is started, we can ban injection mechanism to speed up synchronization time.
    // CPU mining in the main network is absurd, so no one can touch this path, but once the CPU mining synchronization sign is completed, it will ensure that private network work is also an independent miner node.
    atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
}
go s.miner.Start(eb)//Start mining work concurrently
return nil

}
Finally, through miner.Start(eb), call to work - > agent - > CPUAgent - > Update - > seal and go back to our top entry.

In addition, the mining mechanism is analyzed from miner.start() as an entry, and the above StartMining function is before miner.start(). That's how the whole line is strung together.

Go grammar supplement: Comma-ok assertion
if clique, ok := s.engine.(*clique.Clique); ok {

This sentence is confusing. After searching, the above grammar is called Comma-ok assertion.

value, ok = element.(T)
Value is the value of element variable, ok is the Boolean type used to express the assertion result, element is the interface variable, T is the assertion type.

Insert the above code snippet and translate it into:

If s.engine is of Clique type, ok is true, and clique is equal to s.engine.

Second: What role does Snapshot play?
Snapshot objects are acquired in the Sol method by calling the snapshot constructor. The snapshot constructor has a long function body, including the processing of the new Snapshot method and the load Snapshot method. From this analysis, we can also know that Snapshot is a snapshot and a caching mechanism, and it is not only a cache, because it stores the map collection of the most recent signer.

Snapshot can be retrieved or stored from memory (that is, variables in programs) or on disk (that is, through database leveldb), which is actually the concept of secondary caching.

Third: Equal opportunity for certification nodes to block out
First, the source code legacy code snippet of the Seal method above is shown below.

for seen, recent := range snap.Recents {

if recent == signer {
    if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
        log.Info("Signed recently, must wait for others")
        <-stop
        return nil, nil
    }
}

}
among

if recent == signer {
If the current node has recently signed, then skip, in order to ensure equal opportunities, to avoid a certification node can continue to block, thus doing evil.

if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
In fact, the decision of block rights has already been made here. Let's look at it in turn.

snap.Signers are all authentication nodes.
The limit value is half of the number of authentication nodes plus 1, which means that the number of authentication nodes with limit > 50% is guaranteed (security considerations: control of more than 50%). Combining the above equalization of opportunities, clique requires authentication nodes to generate only one block in each round of limit blocks.
Number is the current block number
seen is the index of Recents in "for see, recent: = range snap. Recents {", starting from 0, the maximum value is - 1 of the total number of Recents.
Next, we analyze the conditional expression of the termination of the control program:

number < limit || seen > number-limit
Number < limit, if the block height is less than limit
See > number - limit, the latest issuer number in the cache has exceeded the difference between block height and limit. number-limit is the largest number of bad nodes, and if the index seed is larger than the bad node, it should also be interrupted (TODO: relationship between number block height and authentication node)
In both cases, the program is interrupted, the signature and the block operation are stopped.

IV: Difficulty in Blocking
// The inturn function returns whether the issuer is in the round by a given block height and issuer
func (s *Snapshot) inturn(number uint64, signer common.Address) bool {

// The content of the method body is whether the remainder of the block height and the set length of the authenticated issuer equals the subscript value of the issuer.
signers, offset := s.signers(), 0
for offset < len(signers) && signers[offset] != signer {
    offset++
}
return (number % uint64(len(signers))) == uint64(offset)

}
In short, clique requires the issuer to block in dictionary order according to its set of authenticated issuers in snapshot.

If the above conditions are met, the difficulty is 2, otherwise it is 1.

diffInTurn = big.NewInt(2)// Signature block difficulty in the round is 2.
diffNoTurn = big.NewInt(1)// / Block difficulty that is not in the round is 1.
The difficulty of clique is easy to understand. It's part of the POW special book, but it's very simple in clique. When the inturn node is offline, other nodes will compete, and the difficulty value is reduced to 1. However, all authentication nodes in limit include an inturn and other noturn nodes. Clique supports the inturn first out of block by adding delay time to noturn to avoid needless generation of blocks by noturn nodes. This part of the code is pasted again below.

Wiggle: = time. Duration (len (snap. Signers) / 2 + 1) wiggleTime // wiggleTime = 500 time. Millisecond // / Random Delay, allowing concurrent signatures (for each signer)
delay += time.Duration(rand.Int63n(int64(wiggle)))
clique accepts that the chain with the highest difficulty value is the main chain, so the chain composed of blocks from complete inturn nodes will be the ideal main chain.

Five: Block Checking
// The verifySeal function, also located in the clique file, as the name implies, is used by nodes to verify block information broadcast by other nodes.
func (c Clique) verifySeal(chain consensus.ChainReader, header types.Header, parents []*types.Header) error {

// Genesis Block's Words Don't Check
number := header.Number.Uint64()
if number == 0 {
    return errUnknownBlock
}
// Take the required snapshot object and use it to verify the block header and cache it.
snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
if err != nil {
    return err
}

// Processing the authorization key to check whether it violates the set of authenticated signers
signer, err := ecrecover(header, c.signatures)// The Extra field is decrypted from the block header, the signature string is found, and the address information of the signer is obtained. You can jump to the source analysis of the ecrecover y function below.
if err != nil {
    return err
}
if _, ok := snap.Signers[signer]; !ok {
    return errUnauthorized
}
// Equal Opportunities with Seal's Same Processing
for seen, recent := range snap.Recents {
    if recent == signer {
        if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
            return errUnauthorized
        }
    }
}
// Differentiating inturn from inturn and setting block difficulty are also described above.
inturn := snap.inturn(header.Number.Uint64(), signer)
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    return errInvalidDifficulty
}
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    return errInvalidDifficulty
}
return nil

}
Source code analysis of ecrecover y function:

// The ecrecover y function extracts the address of the ETF account from a signature block head
func ecrecover(header types.Header, sigcache lru.ARCCache) (common.Address, error) {

// If the signature has been cached, return it.
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
    return address.(common.Address), nil
}
// Get the signature content from the Extra field of the block head.
if len(header.Extra) < extraSeal {
    return common.Address{}, errMissingSignature
}
signature := header.Extra[len(header.Extra)-extraSeal:]

// The public key and Ethernet address are decrypted from the signature content by cryptography technology.
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)// See below for the specific source code
if err != nil {
    return common.Address{}, err
}
var signer common.Address
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])//The public key is assigned to signer by keccak 256 decryption.

sigcache.Add(hash, signer)//Add cache
return signer, nil

}
The Ecrecover function of crypto package:

func Ecrecover(hash, sig []byte) ([]byte, error) {

return secp256k1.RecoverPubkey(hash, sig)

}
The Ecrecover function uses secp256k1 to decrypt the public key.

Next, we reverse the VerifySeal function to find the location of the call in miner/remote_agent.go.

// The SubmitWork function attempts to inject a pow solution (consensus engine) into the remote agent to return whether the solution is accepted. (You can't be a bad pow at the same time and you can't make any other mistakes, such as no job being pending
func (a *RemoteAgent) SubmitWork(nonce types.BlockNonce, mixDigest, hash common.Hash) bool {

a.mu.Lock()
defer a.mu.Unlock()

// Ensure that the submitted work is not empty
work := a.work[hash]
if work == nil {
    log.Info("Work submitted but none pending", "hash", hash)
    return false
}
// Make sure the engine is real and effective.
result := work.Block.Header()
result.Nonce = nonce
result.MixDigest = mixDigest

if err := a.engine.VerifySeal(a.chain, result); err != nil {//Here, the VerifySeal method is called.
    log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
    return false
}
block := work.Block.WithSeal(result)

// The solution seems to be effective, returning to the miner and notifying the acceptance result.
a.returnCh <- &Result{work, block}
delete(a.work, hash)

return true

}
This SubmitWork is located in pkg of mining. Its main work is to check whether the work itself is empty, the validity of the blocks in the work and the fields contained in the blocks, and then VerifySeal of the blocks. Confirmation of block difficulty value)

Continue to reverse to find the location where the SubmitWork function is called:

// SubmitWork functions can be used by external miners to submit their POW.
func (api *PublicMinerAPI) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool {

return api.agent.SubmitWork(nonce, digest, solution)

}
summary

Block checking is that the external nodes automatically execute PublicMiner API's SubmitWork method, which calls layer by layer. By checking the signature content in the block head, the public key is restored by secp256k1 method. Then the public key is encrypted as an Ethernet address by Keccak 256, and the signature address is obtained, and the local authentication is carried out. Check in the node cache to see if the signature address meets the requirements. Eventually, errUnauthorized errors will not be reported as long as they are checked layer by layer.

Note: The signature signature exists in the Extra field of the block header when the signer address common.Address is in Seal, and then the signature signature is extracted from the block header in VerifySeal. The decryption of the signature is complicated: first, a public key is restored through secp256k1, and then the signer's address common.Address is encrypted by using the public key and Keccak 256.

common.Address itself is the result of Keccak 256 encryption of the node public key. Refer to common/types.go:

// The Hex function returns a 16-forbidden string representing the Ethernet address.
func (a Address) Hex() string {

unchecksummed := hex.EncodeToString(a[:])
sha := sha3.NewKeccak256()//We will not expand here. We can see that the unchecked plaintext Address is encrypted into a standard Ethernet address by Keccak 256 method.
sha.Write([]byte(unchecksummed))
hash := sha.Sum(nil)

result := []byte(unchecksummed)
for i := 0; i < len(result); i++ {
    hashByte := hash[i/2]
    if i%2 == 0 {
        hashByte = hashByte >> 4
    } else {
        hashByte &= 0xf
    }
    if result[i] > '9' && hashByte > 7 {
        result[i] -= 32
    }
}
return "0x" + string(result)

}
Sixth: Operating mechanism of voting-based authentication nodes
Above we analyzed the details of clique authentication node such as blocking, checking and so on. Then the ultimate question arises here: how to confirm whether a common node is an authentication node?

Answer: clique confirms the authentication node based on the voting mechanism.

Let's first look at the voting entity class, which exists in the snapshot source code.

// Vote represents an independent vote that authorizes a signer to change the authorization list.
type Vote struct {

Signer    common.Address `json:"signer"`    // Authorized Signer (by Voting)
Block     uint64         `json:"block"`     // Voting block number
Address   common.Address `json:"address"`   // Voted account, modify its authorization
Authorize bool           `json:"authorize"` // Whether to authorize or de-authorize a voted account

}
This Vote exists in the attribute field of Snapshot, so the voting mechanism can not be separated from Snapshot. Here we re-analyze the Snapshot entity source code again. I will not repeat the annotated content above, but focus directly on the content of the field related to the voting mechanism.

type Snapshot struct {

config   *params.CliqueConfig
sigcache *lru.ARCCache       

Number  uint64                      `json:"number"`  
Hash    common.Hash                 `json:"hash"`    
Signers map[common.Address]struct{} `json:"signers"` // Collection of Authenticated Nodes
Recents map[uint64]common.Address   `json:"recents"` 
Votes   []*Vote                     `json:"votes"`   // The Vote object array above
Tally   map[common.Address]Tally    `json:"tally"`   // Also a custom type, see below

}
Tally structure:

// Tally is a simple scorer to save the current voting score
type Tally struct {

Authorize bool `json:"authorize"` // Authorize true or remove false
Votes     int  `json:"votes"`     // The proposal has won votes

}
In addition, there is a controversial field proposals in Clique entity, which was not analyzed clearly at that time. What is a proposal?

proposal can be applied for joining or removing an authentication node through rpc, which is structured as the address to be operated (node address) and the status (joining or removing)

Definition of certain concepts in voting
The scope of the vote is in the committee, which means all miners.
Concept introduction: checkpoint, checkpoint Interval = 1024, save snapshot to database every 1024 blocks
Concept introduction: Epoch, like ethash, an Epoch is 30,000 blocks
Voting process
First, a member of the Committee (i.e., node miner) calls the proposal method in consensus/clique/api.go via rpc
// Propose injects a new authorization proposal that either authorizes a signer or removes one.
func (api *API) Propose(address common.Address, auth bool) {

api.clique.lock.Lock()
defer api.clique.lock.Unlock()

api.clique.proposals[address] = auth// true: Authorization, false: Removal

}
The proposals submitted by the rpc above are written to the Clique.proposals collection.
After the start of mining, a commitNewWork will be submitted in miner.start(), which involves the method of preparing block head Prepare. We enter into the implementation of clique, which involves the processing of Clique.proposals above:
// If pending proposals exist, vote
if len(addresses) > 0 {

header.Coinbase = addresses[rand.Intn(len(addresses))]//Assign the address of the voting node to the Coinbase field of the block head.
// Following is a random number segment of blocks assembled by proposals.
if c.proposals[header.Coinbase] {
    copy(header.Nonce[:], nonceAuthVote)
} else {
    copy(header.Nonce[:], nonceDropVote)
}

}

// Declarations and initialization of nonceAuthVote and nonceDropVote constants
NonceAuthVote = hexutil. MustDecode ("0xffffffffffffffffffffff")// Necessary Random Number of Authorized Signers
nonceDropVote = hexutil.MustDecode("0x0000000000000000000")// Necessary Random Number to Remove Signers
When the whole block is assembled (the rest is not repeated), it will be broadcast to the external node checkout. If there is no problem with the block being successfully produced, the proposal in the block head will also be recorded on the main chain.
Snapshot is created when a block is generated, and in the snapshot constructor, an application method for processing proposal s is involved.
// apply creates a new authorization by accepting a given block header
func (s Snapshot) apply(headers []types.Header) (*Snapshot, error) {

if len(headers) == 0 {
    return s, nil
}
for i := 0; i < len(headers)-1; i++ {
    if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
        return nil, errInvalidVotingChain
    }
}
if headers[0].Number.Uint64() != s.Number+1 {
    return nil, errInvalidVotingChain
}
snap := s.copy()
// Core Code for Voting Processing
for _, header := range headers {
    // Remove any votes on checkpoint blocks
    number := header.Number.Uint64()
    // If the block height happens to end at Epoch, empty the votes and scorers
    if number%s.config.Epoch == 0 {
        snap.Votes = nil
        snap.Tally = make(map[common.Address]Tally)
    }
    if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
        delete(snap.Recents, number-limit)
    }
    // Decrypt the signer's address from the block head
    signer, err := ecrecover(header, s.sigcache)
    if err != nil {
        return nil, err
    }
    if _, ok := snap.Signers[signer]; !ok {
        return nil, errUnauthorized
    }
    for _, recent := range snap.Recents {
        if recent == signer {
            return nil, errUnauthorized
        }
    }
    snap.Recents[number] = signer

    // Block head authentication, regardless of any previous vote by the signer
    for i, vote := range snap.Votes {
        if vote.Signer == signer && vote.Address == header.Coinbase {
            // Remove the vote from the cache counter
            snap.uncast(vote.Address, vote.Authorize)

            // Remove votes from a time-ordered list
            snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
            break // only one vote allowed
        }
    }
    // Counting new votes from signers
    var authorize bool
    switch {
    case bytes.Equal(header.Nonce[:], nonceAuthVote):
        authorize = true
    case bytes.Equal(header.Nonce[:], nonceDropVote):
        authorize = false
    default:
        return nil, errInvalidVote
    }
    if snap.cast(header.Coinbase, authorize) {
        snap.Votes = append(snap.Votes, &Vote{
            Signer:    signer,
            Block:     number,
            Address:   header.Coinbase,
            Authorize: authorize,
        })
    }
    // Update the list of signers if votes are passed
    if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
        if tally.Authorize {
            snap.Signers[header.Coinbase] = struct{}{}
        } else {
            delete(snap.Signers, header.Coinbase)

            if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
                delete(snap.Recents, number-limit)
            }
            for i := 0; i < len(snap.Votes); i++ {
                if snap.Votes[i].Signer == header.Coinbase {
                    snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize)

                    snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)

                    i--
                }
            }
        }
        // Change accounts directly regardless of previous votes
        for i := 0; i < len(snap.Votes); i++ {
            if snap.Votes[i].Address == header.Coinbase {
                snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
                i--
            }
        }
        delete(snap.Tally, header.Coinbase)
    }
}
snap.Number += uint64(len(headers))
snap.Hash = headers[len(headers)-1].Hash()

return snap, nil

}
The key control code is tally. Votes > len (snap. Signers)/2, which means that the number of votes in the scorer is more than half of the signers, which means that the vote is passed. The following is to change the list cache of authenticated signers in snapshot, and synchronize it to other nodes, and delete the voting-related information.

summary
Clique was thought to be relatively simple and needn't be investigated for such a long time. However, the consensus algorithm of POA is rather difficult. It is based on two different scenarios, POW and POW, and the way to block is totally different. Next, I try to summarize Clique's consensus mechanism in a short language.

The clique consensus is based on the way that the committee elects the authentication node to confirm the block power. The voting method requests proposals through rpc, snapshot secondary cache mechanism, voting, and execution of the voting results. Authentication nodes have equal access to blocks, and the difficulty is determined by rounds (whether to block according to the order of cache authentication). Block head Extra stores signatures, Keccak 256 encrypts ether addresses, secp256k1 decrypts signatures as public keys. Block checking can be backstepped by the logic of authenticating nodes to block out.

So far, we have a profound understanding and understanding of the POA consensus mechanism and the realization of Etherfield clique. We believe that if we want to achieve a set of POA, it is also fully capable. If you have any questions in reading this article, please leave a message to me. I will reply in time.

Posted by rosegarden on Fri, 27 Sep 2019 02:29:28 -0700