Go-ethereum 源碼解析之 consensus/clique/clique.go
// Package clique implements the proof-of-authority consensus engine.
package clique
const (
checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory
inmemorySignatures = 4096 // Number of recent block signatures to keep in memory
wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
)
// Clique proof-of-authority protocol constants.
var (
epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
)
// Various error messages to mark blocks invalid. These should be private to
// prevent engine specific errors from being referenced in the remainder of the
// codebase, inherently breaking if the engine is swapped out. Please put common
// error types into the consensus package.
var (
// errUnknownBlock is returned when the list of signers is requested for a block
// that is not part of the local blockchain.
errUnknownBlock = errors.New("unknown block")
// errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition
// block has a beneficiary set to non-zeroes.
errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero")
// errInvalidVote is returned if a nonce value is something else that the two
// allowed constants of 0x00..0 or 0xff..f.
errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f")
// errInvalidCheckpointVote is returned if a checkpoint/epoch transition block
// has a vote nonce set to non-zeroes.
errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero")
// errMissingVanity is returned if a block's extra-data section is shorter than
// 32 bytes, which is required to store the signer vanity.
errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing")
// errMissingSignature is returned if a block's extra-data section doesn't seem
// to contain a 65 byte secp256k1 signature.
errMissingSignature = errors.New("extra-data 65 byte suffix signature missing")
// errExtraSigners is returned if non-checkpoint block contain signer data in
// their extra-data fields.
errExtraSigners = errors.New("non-checkpoint block contains extra signer list")
// errInvalidCheckpointSigners is returned if a checkpoint block contains an
// invalid list of signers (i.e. non divisible by 20 bytes, or not the correct
// ones).
errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block")
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
errInvalidMixDigest = errors.New("non-zero mix digest")
// errInvalidUncleHash is returned if a block contains an non-empty uncle list.
errInvalidUncleHash = errors.New("non empty uncle hash")
// errInvalidDifficulty is returned if the difficulty of a block is not either
// of 1 or 2, or if the value does not match the turn of the signer.
errInvalidDifficulty = errors.New("invalid difficulty")
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
// the previous block's timestamp + the minimum block period.
ErrInvalidTimestamp = errors.New("invalid timestamp")
// errInvalidVotingChain is returned if an authorization list is attempted to
// be modified via out-of-range or non-contiguous headers.
errInvalidVotingChain = errors.New("invalid voting chain")
// errUnauthorized is returned if a header is signed by a non-authorized entity.
errUnauthorized = errors.New("unauthorized")
// errWaitTransactions is returned if an empty block is attempted to be sealed
// on an instant chain (0 second period). It's important to refuse these as the
// block reward is zero, so an empty block just bloats the chain... fast.
errWaitTransactions = errors.New("waiting for transactions")
)
// SignerFn is a signer callback function to request a hash to be signed by a
// backing account.
type SignerFn func(accounts.Account, []byte) ([]byte, error)
// sigHash returns the hash which is used as input for the proof-of-authority
// signing. It is the hash of the entire header apart from the 65 byte signature
// contained at the end of the extra data.
//
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
// panics. This is done to avoid accidentally using both forms (signature present
// or not), which could be abused to produce different hashes for the same header.
func sigHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
})
hasher.Sum(hash[:0])
return hash
}
// ecrecover extracts the Ethereum account address from a signed header.
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
// If the signature's already cached, return that
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < extraSeal {
return common.Address{}, errMissingSignature
}
signature := header.Extra[len(header.Extra)-extraSeal:]
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)
if err != nil {
return common.Address{}, err
}
var signer common.Address
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
sigcache.Add(hash, signer)
return signer, nil
}
// Clique is the proof-of-authority consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks.
type Clique struct {
config *params.CliqueConfig // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints
recents *lru.ARCCache // Snapshots for recent block to speed up reorgs
signatures *lru.ARCCache // Signatures of recent blocks to speed up mining
proposals map[common.Address]bool // Current list of proposals we are pushing
signer common.Address // Ethereum address of the signing key
signFn SignerFn // Signer function to authorize hashes with
lock sync.RWMutex // Protects the signer fields
}
// New creates a Clique proof-of-authority consensus engine with the initial
// signers set to the ones provided by the user.
func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
// Set any missing consensus parameters to their defaults
conf := *config
if conf.Epoch == 0 {
conf.Epoch = epochLength
}
// Allocate the snapshot caches and create the engine
recents, _ := lru.NewARC(inmemorySnapshots)
signatures, _ := lru.NewARC(inmemorySignatures)
return &Clique{
config: &conf,
db: db,
recents: recents,
signatures: signatures,
proposals: make(map[common.Address]bool),
}
}
// Author implements consensus.Engine, returning the Ethereum address recovered
// from the signature in the header's extra-data section.
func (c *Clique) Author(header *types.Header) (common.Address, error) {
return ecrecover(header, c.signatures)
}
// VerifyHeader checks whether a header conforms to the consensus rules.
func (c *Clique) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
return c.verifyHeader(chain, header, nil)
}
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
// method returns a quit channel to abort the operations and a results channel to
// retrieve the async verifications (the order is that of the input slice).
func (c *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
abort := make(chan struct{})
results := make(chan error, len(headers))
go func() {
for i, header := range headers {
err := c.verifyHeader(chain, header, headers[:i])
select {
case <-abort:
return
case results <- err:
}
}
}()
return abort, results
}
// verifyHeader checks whether a header conforms to the consensus rules.The
// caller may optionally pass in a batch of parents (ascending order) to avoid
// looking those up from the database. This is useful for concurrently verifying
// a batch of new headers.
func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
if header.Number == nil {
return errUnknownBlock
}
number := header.Number.Uint64()
// Don't waste time checking blocks from the future
if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 {
return consensus.ErrFutureBlock
}
// Checkpoint blocks need to enforce zero beneficiary
checkpoint := (number % c.config.Epoch) == 0
if checkpoint && header.Coinbase != (common.Address{}) {
return errInvalidCheckpointBeneficiary
}
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
if !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) {
return errInvalidVote
}
if checkpoint && !bytes.Equal(header.Nonce[:], nonceDropVote) {
return errInvalidCheckpointVote
}
// Check that the extra-data contains both the vanity and signature
if len(header.Extra) < extraVanity {
return errMissingVanity
}
if len(header.Extra) < extraVanity+extraSeal {
return errMissingSignature
}
// Ensure that the extra-data contains a signer list on checkpoint, but none otherwise
signersBytes := len(header.Extra) - extraVanity - extraSeal
if !checkpoint && signersBytes != 0 {
return errExtraSigners
}
if checkpoint && signersBytes%common.AddressLength != 0 {
return errInvalidCheckpointSigners
}
// Ensure that the mix digest is zero as we don't have fork protection currently
if header.MixDigest != (common.Hash{}) {
return errInvalidMixDigest
}
// Ensure that the block doesn't contain any uncles which are meaningless in PoA
if header.UncleHash != uncleHash {
return errInvalidUncleHash
}
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
if number > 0 {
if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) {
return errInvalidDifficulty
}
}
// If all checks passed, validate any special fields for hard forks
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
return err
}
// All basic checks passed, verify cascading fields
return c.verifyCascadingFields(chain, header, parents)
}
// verifyCascadingFields verifies all the header fields that are not standalone,
// rather depend on a batch of previous headers. The caller may optionally pass
// in a batch of parents (ascending order) to avoid looking those up from the
// database. This is useful for concurrently verifying a batch of new headers.
func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
// The genesis block is the always valid dead-end
number := header.Number.Uint64()
if number == 0 {
return nil
}
// Ensure that the block's timestamp isn't too close to it's parent
var parent *types.Header
if len(parents) > 0 {
parent = parents[len(parents)-1]
} else {
parent = chain.GetHeader(header.ParentHash, number-1)
}
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
return consensus.ErrUnknownAncestor
}
if parent.Time.Uint64()+c.config.Period > header.Time.Uint64() {
return ErrInvalidTimestamp
}
// Retrieve the snapshot needed to verify this header and cache it
snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
if err != nil {
return err
}
// If the block is a checkpoint block, verify the signer list
if number%c.config.Epoch == 0 {
signers := make([]byte, len(snap.Signers)*common.AddressLength)
for i, signer := range snap.signers() {
copy(signers[i*common.AddressLength:], signer[:])
}
extraSuffix := len(header.Extra) - extraSeal
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) {
return errInvalidCheckpointSigners
}
}
// All basic checks passed, verify the seal and return
return c.verifySeal(chain, header, parents)
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
// Search for a snapshot in memory or on disk for checkpoints
var (
headers []*types.Header
snap *Snapshot
)
for snap == nil {
// If an in-memory snapshot was found, use that
if s, ok := c.recents.Get(hash); ok {
snap = s.(*Snapshot)
break
}
// If an on-disk checkpoint snapshot can be found, use that
if number%checkpointInterval == 0 {
if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash)
snap = s
break
}
}
// If we're at an checkpoint block, make a snapshot if it's known
if number == 0 || (number%c.config.Epoch == 0 && chain.GetHeaderByNumber(number-1) == nil) {
checkpoint := chain.GetHeaderByNumber(number)
if checkpoint != nil {
hash := checkpoint.Hash()
signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(signers); i++ {
copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
}
snap = newSnapshot(c.config, c.signatures, number, hash, signers)
if err := snap.store(c.db); err != nil {
return nil, err
}
log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)
break
}
}
// No snapshot for this header, gather the header and move backward
var header *types.Header
if len(parents) > 0 {
// If we have explicit parents, pick from there (enforced)
header = parents[len(parents)-1]
if header.Hash() != hash || header.Number.Uint64() != number {
return nil, consensus.ErrUnknownAncestor
}
parents = parents[:len(parents)-1]
} else {
// No explicit parents (or no more left), reach out 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
}
// Previous snapshot found, apply any pending headers 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)
if err != nil {
return nil, err
}
c.recents.Add(snap.Hash, snap)
// If we've generated a new checkpoint snapshot, save 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
}
// VerifyUncles implements consensus.Engine, always returning an error for any
// uncles as this consensus mechanism doesn't permit uncles.
func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
if len(block.Uncles()) > 0 {
return errors.New("uncles not allowed")
}
return nil
}
// VerifySeal implements consensus.Engine, checking whether the signature contained
// in the header satisfies the consensus protocol requirements.
func (c *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
return c.verifySeal(chain, header, nil)
}
// verifySeal checks whether the signature contained in the header satisfies the
// consensus protocol requirements. The method accepts an optional list of parent
// headers that aren't yet part of the local blockchain to generate the snapshots
// from.
func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
// Verifying the genesis block is not supported
number := header.Number.Uint64()
if number == 0 {
return errUnknownBlock
}
// Retrieve the snapshot needed to verify this header and cache it
snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
if err != nil {
return err
}
// Resolve the authorization key and check against signers
signer, err := ecrecover(header, c.signatures)
if err != nil {
return err
}
if _, ok := snap.Signers[signer]; !ok {
return errUnauthorized
}
for seen, recent := range snap.Recents {
if recent == signer {
// Signer is among recents, only fail if the current block doesn't shift it out
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
return errUnauthorized
}
}
}
// Ensure that the difficulty corresponds to the turn-ness of the signer
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
}
// Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top.
func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) error {
// If the block isn't a checkpoint, cast a random vote (good enough for now)
header.Coinbase = common.Address{}
header.Nonce = types.BlockNonce{}
number := header.Number.Uint64()
// Assemble the voting snapshot to check which votes make sense
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return err
}
if number%c.config.Epoch != 0 {
c.lock.RLock()
// Gather all the proposals that make sense voting on
addresses := make([]common.Address, 0, len(c.proposals))
for address, authorize := range c.proposals {
if snap.validVote(address, authorize) {
addresses = append(addresses, address)
}
}
// If there's pending proposals, cast a vote on them
if len(addresses) > 0 {
header.Coinbase = addresses[rand.Intn(len(addresses))]
if c.proposals[header.Coinbase] {
copy(header.Nonce[:], nonceAuthVote)
} else {
copy(header.Nonce[:], nonceDropVote)
}
}
c.lock.RUnlock()
}
// Set the correct difficulty
header.Difficulty = CalcDifficulty(snap, c.signer)
// Ensure the extra data has all it's components
if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
}
header.Extra = header.Extra[:extraVanity]
if number%c.config.Epoch == 0 {
for _, signer := range snap.signers() {
header.Extra = append(header.Extra, signer[:]...)
}
}
header.Extra = append(header.Extra, make([]byte, extraSeal)...)
// Mix digest is reserved for now, set to empty
header.MixDigest = common.Hash{}
// Ensure the timestamp has the correct delay
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period))
if header.Time.Int64() < time.Now().Unix() {
header.Time = big.NewInt(time.Now().Unix())
}
return nil
}
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given, and returns the final block.
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// No block rewards in PoA, so the state remains as is and uncles are dropped
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = types.CalcUncleHash(nil)
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts), nil
}
// Authorize injects a private key into the consensus engine to mint new blocks
// with.
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
c.lock.Lock()
defer c.lock.Unlock()
c.signer = signer
c.signFn = signFn
}
// Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials.
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
header := block.Header()
// Sealing the genesis block is not supported
number := header.Number.Uint64()
if number == 0 {
return errUnknownBlock
}
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
if c.config.Period == 0 && len(block.Transactions()) == 0 {
return errWaitTransactions
}
// Don't hold the signer fields for the entire sealing procedure
c.lock.RLock()
signer, signFn := c.signer, c.signFn
c.lock.RUnlock()
// Bail out if we're unauthorized to sign a block
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return err
}
if _, authorized := snap.Signers[signer]; !authorized {
return errUnauthorized
}
// If we're amongst the recent signers, wait for the next block
for seen, recent := range snap.Recents {
if recent == signer {
// Signer is among recents, only wait if the current block doesn't shift it out
if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
log.Info("Signed recently, must wait for others")
return nil
}
}
}
// Sweet, the protocol permits us to sign the block, wait for our time
delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
if header.Difficulty.Cmp(diffNoTurn) == 0 {
// It's not our turn explicitly to sign, delay it a bit
wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
delay += time.Duration(rand.Int63n(int64(wiggle)))
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
}
// Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
if err != nil {
return err
}
copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
// Wait until sealing is terminated or delay timeout.
log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
go func() {
select {
case <-stop:
return
case <-time.After(delay):
}
select {
case results <- block.WithSeal(header):
default:
log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
}
}()
return nil
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
if err != nil {
return nil
}
return CalcDifficulty(snap, c.signer)
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
if snap.inturn(snap.Number+1, signer) {
return new(big.Int).Set(diffInTurn)
}
return new(big.Int).Set(diffNoTurn)
}
// SealHash returns the hash of a block prior to it being sealed.
func (c *Clique) SealHash(header *types.Header) common.Hash {
return sigHash(header)
}
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
func (c *Clique) Close() error {
return nil
}
// APIs implements consensus.Engine, returning the user facing RPC API to allow
// controlling the signer voting.
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
return []rpc.API{{
Namespace: "clique",
Version: "1.0",
Service: &API{chain: chain, clique: c},
Public: false,
}}
}
Appendix A. 總體批注
包 clique 實(shí)現(xiàn)了 proof-of-authority 共識(shí)引擎。
文件 consensus/clique/clique.go 主要實(shí)現(xiàn)了 Clique 共識(shí)引擎嵌言。同時(shí),定義了一些 Clique 共識(shí)引擎特定的常量與錯(cuò)誤消息苛白,以及與簽名相關(guān)的幾個(gè)輔助函數(shù)。
對(duì)于 Clique躏率,要特別注意區(qū)塊或區(qū)塊頭是如何在其各方法中進(jìn)行流轉(zhuǎn)的蓬抄,即對(duì)于一個(gè)原始區(qū)塊,是如何最終確定共識(shí)協(xié)議相關(guān)的各字段的峭状,并挖出帶有簽名的最終區(qū)塊,等待網(wǎng)絡(luò)中的其它節(jié)點(diǎn)確認(rèn)胆敞。如:
- 方法 Prepare() 對(duì)于給定的區(qū)塊頭 header,逐一計(jì)算其 PoA 共識(shí)協(xié)議特定的字段,主要是 Coinbase频蛔、Nonce、Extra 三部分三圆、Difficulty、Time 等,用于表示投票區(qū)塊、檢查點(diǎn)區(qū)塊现柠、區(qū)塊簽名等比然。
- 方法 Finalize() 根據(jù) PoA 共識(shí)協(xié)議最后一次修正區(qū)塊頭中的字段 Root 和 UncleHash,這兩個(gè)字段在方法 Prepare() 中沒(méi)有被設(shè)置。同時(shí)蓖墅,將區(qū)塊頭 header,事務(wù)列表 txs贪壳,收據(jù)列表 receipts 這些最重要的信息組裝進(jìn)區(qū)塊中,并將組裝后的區(qū)塊進(jìn)行簽名,從而產(chǎn)生本地節(jié)點(diǎn)挖出來(lái)的新區(qū)塊膘掰,等待網(wǎng)絡(luò)中其它節(jié)點(diǎn)進(jìn)行確認(rèn)。
- 方法 Seal() 給方法 Finalize() 組裝的區(qū)塊添加簽名,并將簽名后的區(qū)塊發(fā)送給結(jié)果通道 results惠豺,用于驅(qū)動(dòng)本地節(jié)點(diǎn)挖出新區(qū)塊后所應(yīng)進(jìn)行的后續(xù)流程蛹疯。
- 方法 snapshot() 用于從緩存或數(shù)據(jù)庫(kù)中加載某個(gè)區(qū)塊編號(hào)對(duì)應(yīng)的快照。快照有兩種:一種檢查點(diǎn)快照寞钥,另一種是投票快照。檢查點(diǎn)快照用于確定授權(quán)簽名者列表,并在下一個(gè)檢查點(diǎn)快照之前對(duì)所有區(qū)塊中的投票進(jìn)行處理邻吭,從而更新下一個(gè)檢查點(diǎn)快照。投票快照用于部分應(yīng)用投票。
Appendix B. 啟動(dòng)的協(xié)程
1. 匿名協(xié)程
- 方法 VerifyHeaders() 中啟動(dòng)了一個(gè)獨(dú)立的匿名協(xié)程,用于驗(yàn)證區(qū)塊頭列表并將驗(yàn)證結(jié)果發(fā)送到結(jié)果通道中千所,同時(shí)從退出通道監(jiān)聽(tīng)中止消息以決定是否提前退出協(xié)程。
- 方法 Seal() 中啟動(dòng)了一個(gè)獨(dú)立的匿名協(xié)程籽孙,用于在延遲特定的細(xì)微時(shí)間之后將帶有簽名的區(qū)塊發(fā)送給結(jié)果通道础倍,用于表明本地節(jié)點(diǎn)已經(jīng)挖出了一個(gè)新區(qū)塊忆家。
Appendix C. 詳細(xì)批注
1. const
checkpointInterval = 1024: 每隔多少個(gè)區(qū)塊之后將投票快照保存到數(shù)據(jù)庫(kù)。
inmemorySnapshots = 128: 內(nèi)存中將保留的最近投票快照數(shù)量。即緩存多少個(gè) clique.Snapshot筷转。
inmemorySignatures = 4096: 內(nèi)存中將保留的最近區(qū)塊簽名者的數(shù)量。即緩存多少個(gè) common.Address。
wiggleTime = 500 * time.Millisecond: 每個(gè)簽名者各自延遲隨機(jī)數(shù)量的時(shí)間,以允許簽名者可以并發(fā)簽名乡范。
2. var
定義了 Clique proof-of-authority 協(xié)議常量。
epochLength = uint64(30000): 每隔多少個(gè)區(qū)塊設(shè)置一個(gè)檢查點(diǎn),并重置所有待處理的投票索赏。
extraVanity = 32: types.Header 字段 Extra 的前 32 個(gè)字節(jié)。表示以太坊協(xié)議可以授受 32 個(gè)字節(jié)的特定信息,這一點(diǎn)上沒(méi)有破壞 PoW 共識(shí)引擎威鹿?
extraSeal = 65: types.Header 字段 Extra 的最后 65 個(gè)字節(jié)臂容。表示簽名者對(duì)區(qū)塊簽名哈希值的簽名信息糟秘。
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff"): 魔數(shù),表示投票新增一個(gè)簽名者。
nonceDropVote = hexutil.MustDecode("0x0000000000000000"): 魔數(shù)泻蚊,表示投票解除一個(gè)簽名者羹奉。types.Header 的字段 Nonce 默認(rèn)為魔數(shù) nonceDropVote迁筛。
uncleHash = types.CalcUncleHash(nil): 總是返回哈希值 Keccak256(RLP([])),因?yàn)槭鍏^(qū)塊在 PoW 共識(shí)協(xié)議之外沒(méi)有任何意義蜘犁。
diffInTurn = big.NewInt(2): 魔數(shù),in-turn 簽名時(shí)的區(qū)塊難度值。即根據(jù) snapshot.go 中對(duì)于 in-turn 簽名的規(guī)則筑凫,在簽名為 in-turn 時(shí)滓技,type.Headers 的字段 Difficulty 應(yīng)該為魔數(shù) diffInTurn。默認(rèn)為 diffNoTurn叠必?
diffNoTurn = big.NewInt(1): 魔數(shù),非 in-turn(或 out-of-turn)簽名時(shí)的區(qū)塊難度值妹窖。在簽名為 out-of-turn 時(shí)纬朝,type.Headers 的字段 Difficulty 應(yīng)該為魔數(shù) diffNoTurn。
3. var
定義了各種錯(cuò)誤消息以表示區(qū)塊是無(wú)效的骄呼。這些錯(cuò)誤消息應(yīng)該是私有的,以防止在代碼庫(kù)的其余部分引用與特定共識(shí)引擎相關(guān)的錯(cuò)誤消息蜓萄,如果共識(shí)引擎被替換掉則會(huì)從本質(zhì)上破壞代碼庫(kù)隅茎。請(qǐng)將常見(jiàn)的錯(cuò)誤類型放入包 consensus 中,即文件 consensus/error.go 中嫉沽。
- errUnknownBlock = errors.New("unknown block"): 當(dāng)為不屬于本地區(qū)塊鏈的區(qū)塊請(qǐng)求簽名者列表時(shí)辟犀,將返回 errUnknownBlock。
- errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero"): 如果處理檢查點(diǎn)位置的區(qū)塊的礦工地址不為 0x0耻蛇,即 types.Header 的字段 Coinbase 不為 0x0踪蹬,將返回 errInvalidCheckpointBeneficiary胞此。
- errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f"): 當(dāng) types.Header 中的字段 Nonce 的值不是魔數(shù) nonceAuthVote 和 nonceDropVote 中的一個(gè)時(shí),將返回 errInvalidVote跃捣。
- errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero"): 對(duì)處理檢查點(diǎn)的區(qū)塊頭來(lái)說(shuō)漱牵,如果 types.Header 中的字段 Nonce 不是 nonceDropVote 時(shí),將返回 errInvalidCheckpointVote疚漆。
- errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing"): 當(dāng) types.Header 中的字段 Extra 的字節(jié)數(shù)小于 extraVanity (32) 時(shí)酣胀,將返回 errMissingVanity。
- errMissingSignature = errors.New("extra-data 65 byte suffix signature missing"): 當(dāng) types.Header 中的字段 Extra 的字節(jié)數(shù)小于 extraVanity + extraSeal (32 + 65 = 97) 時(shí)娶聘,即無(wú)法包含 65 個(gè)字節(jié)的 secp256k1 簽名信息闻镶。
- errExtraSigners = errors.New("non-checkpoint block contains extra signer list"): 對(duì)于處于非檢查點(diǎn)位置的區(qū)塊來(lái)說(shuō),如果其 types.Header 中的字段 Extra 中包含了簽名信息丸升,將返回 errExtraSigners铆农。
- errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block"): 對(duì)于處于檢查點(diǎn)位置的區(qū)塊來(lái)說(shuō),如果其 types.Header 字段中的 Extra 字段包含的簽名者列表是無(wú)效的(如 types.Header.Extra[extraVanity: len(types.Header.Extra) - extraSeal] 的字節(jié)數(shù)不是 20 的倍數(shù)狡耻,或者這些字節(jié)與 clique.Snapshot 中的方法 signers() 返回的有序簽名者列表不一致)墩剖,將返回 errInvalidCheckpointSigners。
- 注意:根據(jù) Clique.verifyCascadingFields() 返回 errInvalidCheckpointSigners 的代碼可知夷狰,types.Header 的字段 Extra 中包含的授權(quán)簽名者列表必須是升序存儲(chǔ)的岭皂。這是 Clique 共識(shí)引擎的協(xié)議規(guī)則。
- errInvalidMixDigest = errors.New("non-zero mix digest"): 如果區(qū)塊頭 types.Header 的字段 MixDigest 不為 common.Hash{} 的話沼头,將返回 errInvalidMixDigest爷绘。
- errInvalidUncleHash = errors.New("non empty uncle hash"): 如果區(qū)塊頭 types.Header 的字段 UncleHash 不為 uncleHash 時(shí),將返回 errInvalidUncleHash进倍。
- 在 PoA 共識(shí)協(xié)議里土至,叔區(qū)塊列表為空。
- errInvalidDifficulty = errors.New("invalid difficulty"): 如果區(qū)塊的難度背捌,即 types.Header 的字段 Difficulty 既不是 diffInTurn 也不是 diffNoTurn 時(shí)毙籽,或者當(dāng)簽名者是 in-turn 簽名但 Difficulty 不為 diffInTurn,或者當(dāng)簽名者是 no-turn 簽名但 Difficulty 不為 diffNoTurn毡庆,將返回 errInvalidDifficulty坑赡。
- in-turn 就是簽名者簽名的區(qū)塊正好是該簽名者的輪次,否則為 no-turn 簽名
- in-turn 和 no-turn 的具體解釋可以參考 consensus/consensus.go
- ErrInvalidTimestamp = errors.New("invalid timestamp"): 如果區(qū)塊被打包的時(shí)間戳么抗,即 types.Header 的字段 Time毅否,小于父區(qū)塊的打包時(shí)間戳加上最小出塊周期,將返回 ErrInvalidTimestamp蝇刀。
- 即區(qū)塊的出塊頻率不然過(guò)快螟加。
- errInvalidVotingChain = errors.New("invalid voting chain"): 當(dāng)授權(quán)簽名者列表嘗試修改不連續(xù)的區(qū)塊頭列表,或者超出范圍的區(qū)塊頭列表時(shí),將返回 errInvalidVotingChain捆探。
- 用于確保區(qū)塊從編號(hào) 0 開(kāi)始然爆,不斷連續(xù)地被簽名。確保區(qū)塊鏈的完整性和連續(xù)性黍图。
- errUnauthorized = errors.New("unauthorized"): 如果區(qū)塊頭由一個(gè)不在授權(quán)簽名者列表中的賬戶進(jìn)行簽名曾雕,或者授權(quán)簽名者在最近連續(xù) K/2 + 1 個(gè)區(qū)塊內(nèi)簽名超過(guò) 1 次時(shí),將返回 errUnauthorized助被。
- 用于在簽名者的簽名不符合 PoA 共識(shí)協(xié)議時(shí)返回錯(cuò)誤消息剖张。
- 這個(gè)錯(cuò)誤消息只在驗(yàn)證簽名,或應(yīng)用簽名時(shí)揩环,才會(huì)出現(xiàn)搔弄。而在驗(yàn)證區(qū)塊頭時(shí),可不詳細(xì)驗(yàn)證具體的簽名信息丰滑。當(dāng)然顾犹,這和具體的實(shí)現(xiàn)有關(guān)。目前實(shí)現(xiàn)中吨枉,檢驗(yàn)簽名信息是否符合 PoA 共識(shí)協(xié)議檬寂,主要在這些方法中:Clique.VerifySeal(), Clique.Seal(), Snapshot.apply()
- 方法 Clique.VerifySeal() 應(yīng)該是本地節(jié)點(diǎn)驗(yàn)證網(wǎng)絡(luò)中其它節(jié)點(diǎn)打包出的區(qū)塊的簽名信息是否有效钠惩。
- 方法 Clique.Seal() 是本地節(jié)點(diǎn)對(duì)最終將要打包的區(qū)塊的區(qū)塊頭進(jìn)行簽名,簽名之后區(qū)塊 types.Block 相關(guān)的整體結(jié)構(gòu)就被確認(rèn)下來(lái)靠柑,不然再進(jìn)行修改了认臊。
- 方法 Snapshot.apply() 將根據(jù)各區(qū)塊中包含的投票來(lái)實(shí)際修改授權(quán)簽名者列表圃庭,并對(duì)各區(qū)塊的簽名信息進(jìn)行一次檢驗(yàn)。
- errWaitTransactions = errors.New("waiting for transactions"): 如果嘗試在即時(shí)鏈(0 秒周期)上簽名空塊(即區(qū)塊中不包含任何事務(wù))失晴,將返回 errWaitTransactions剧腻。拒絕這些是非常重要的,因?yàn)閰^(qū)塊獎(jiǎng)勵(lì)為零涂屁,所以一個(gè)空塊只會(huì)使區(qū)塊鏈快速膨脹书在。
4. type SignerFn func(accounts.Account, []byte) ([]byte, error)
SignerFn 是具體簽名函數(shù)的抽象,最終描述一個(gè)具體的簽名函數(shù)拆又。簽名函數(shù)返回賬戶對(duì)于哈希值的簽名信息儒旬。這里的哈希為 types.Header 中去掉字段 Extra 最后 65 個(gè)字節(jié)的哈希值。
文件 go-ethereum/accounts/accounts.go 中包含了具體的簽名函數(shù) accounts.SignHash()帖族,這個(gè)函數(shù)為 Clique 共識(shí)引擎所采用的具體的簽名算法栈源。
文件:go-ethereum/consensus/clique/clique.go
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
c.lock.Lock()
defer c.lock.Unlock()
c.signer = signer
c.signFn = signFn
}
文件:go-ethereum/eth/backend.go
func (s *Ethereum) StartMining(threads int) error {
...
clique.Authorize(eb, wallet.SignHash)
...
}
5. func sigHash(header *types.Header) (hash common.Hash)
方法 sigHash() 返回給定區(qū)塊頭的哈希值,該哈希值用作權(quán)限證明簽名的輸入竖般。除了包含在區(qū)塊頭 types.Header 中的字段 Extra 末尾的 65 字節(jié)簽名之外甚垦,它是整個(gè)區(qū)塊頭的散列。
注意,該方法要求額外數(shù)據(jù)至少為 65 字節(jié)艰亮,否則會(huì)引起 panic闭翩。這樣做是為了避免意外地使用兩種形式(簽名存在與否),這些形式可能被濫用以產(chǎn)生相同區(qū)塊頭的不同散列迄埃。
對(duì)于區(qū)塊頭存在兩種比較重要的散列:
- 整個(gè)區(qū)塊頭的散列疗韵,也稱作區(qū)塊散列。
- 不包含區(qū)塊頭額外數(shù)據(jù)最后 65 字節(jié)的簽名散列调俘。說(shuō)是簽名散列伶棒,其實(shí)更應(yīng)該說(shuō)是給簽名用的散列。
上述想要避免的應(yīng)該是彩库,雖然額外數(shù)據(jù)是否包含 65 字節(jié)對(duì)簽名散列沒(méi)有任何差別肤无,但對(duì)應(yīng)的區(qū)塊散列卻是完全不同的。
主要的實(shí)現(xiàn)細(xì)節(jié)如下:
- 通過(guò)方法 sha3.NewKeccak256() 獲得哈希器 keccak256骇钦。
- 該哈希器實(shí)現(xiàn)了 hash.Hash 接口宛渐。
- 該哈希器繼承了 io.Writer 接口。
- 通過(guò)方法 rlp.Encode() 將簽名散列對(duì)應(yīng)的區(qū)塊頭中的數(shù)據(jù)編碼為 RLP 二進(jìn)制流存入哈希器 keccak256 中眯搭。
- 調(diào)用哈希器的方法 Sum() 返回最終的簽名散列窥翩。
6. func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error)
函數(shù) ecrecover() 從已經(jīng)簽名的區(qū)塊頭中提取以太坊賬戶地址。
可以簡(jiǎn)單地把參數(shù) sigcache 理解為 mapping[common.Hash]common.Address 結(jié)構(gòu)鳞仙。用于緩存賬戶地址對(duì)某個(gè)區(qū)塊的簽名寇蚊,這里某個(gè)區(qū)塊用其區(qū)塊哈希值惟一標(biāo)識(shí)。
主要的實(shí)現(xiàn)細(xì)節(jié)如下:
- 先從緩存 sigcache 中查找對(duì)應(yīng)區(qū)塊哈希的簽名者棍好,即以太坊賬戶地址仗岸。
- 從區(qū)塊頭額外數(shù)據(jù)中提取簽名,失敗時(shí)返回 errMissingSignature借笙。
- 通過(guò)函數(shù) crypto.Ecrecover() 從區(qū)塊簽名哈希和簽名信息恢復(fù)出公鑰扒怖。
- 通過(guò)函數(shù) crypto.Keccak256() 對(duì)公鑰進(jìn)行二次散列,并將散列的前 20 個(gè)字節(jié)作為賬戶地址业稼。
- 先緩存賬戶地址到參數(shù) sigcache 中盗痒,再返回賬戶地址。
7. type Clique struct
Clique 是在發(fā)生 Ropsten 攻擊之后被提議支持以太坊測(cè)試網(wǎng)絡(luò)的權(quán)威證明共識(shí)引擎低散。
config *params.CliqueConfig: Clique 共識(shí)引擎特定的配置參數(shù)俯邓,主要是參數(shù) epoch 和 period。
db ethdb.Database: 存儲(chǔ)和查詢檢查點(diǎn)快照的數(shù)據(jù)庫(kù)谦纱。底層數(shù)據(jù)庫(kù) LevelDB 與以太坊之間的接口層看成。
recents *lru.ARCCache: 最近區(qū)塊的快照可以加快重組速度。內(nèi)存中緩存的最近 inmemorySnapshots 個(gè)快照跨嘉,可簡(jiǎn)單理解為 map[hash(clique.Snapshot)]clique.Snapshot川慌。
signatures *lru.ARCCache: 最近的區(qū)塊的簽名吃嘿,以加快挖礦。內(nèi)存中緩存的最近 inmemorySignatures 個(gè)簽名者梦重,可簡(jiǎn)單理解為 map[types.Header.Hash()]common.Address
proposals map[common.Address]bool: 我們正在推動(dòng)的當(dāng)前提案列表兑燥。這是的提案是指投票者本身投出的是授權(quán)投票還是解除授權(quán)投票,而不是被投票者得到的是授權(quán)投票還是被授權(quán)投票琴拧。這也符合 map 結(jié)構(gòu)降瞳,因?yàn)楸煌镀闭呤强梢垣@得多張投票的,而投票者則只能投出一張投票蚓胸。當(dāng)前提案列表應(yīng)該是緩存的意思挣饥,而且是最新的,Clique 共識(shí)引擎可以直接拿來(lái)用于判定某個(gè)投票者的投票內(nèi)容沛膳。
signer common.Address: 簽名密鑰的以太網(wǎng)地址扔枫。??? 是指本地節(jié)點(diǎn)的簽名者嗎?即本地節(jié)點(diǎn)的礦工锹安?
signFn SignerFn: 簽名者函數(shù)用于授權(quán)哈希短荐。即礦工采用的具體簽名函數(shù),其中密鑰為礦工地址叹哭,被簽名信息為區(qū)塊簽名哈希忍宋。文件 go-ethereum/accounts/accounts.go 中包含了具體的簽名函數(shù) accounts.SignHash(),這個(gè)函數(shù)為 Clique 共識(shí)引擎所采用的具體的簽名算法风罩。
lock sync.RWMutex: 用于保護(hù)字段 signer 的鎖糠排。
(1) func New(config *params.CliqueConfig, db ethdb.Database) *Clique
構(gòu)造函數(shù) New() 創(chuàng)建子 Clique 權(quán)威證明共識(shí)引擎。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 設(shè)置配置參數(shù)中未設(shè)置的參數(shù)超升。
- 設(shè)置緩存字段 recents 的大小 inmemorySnapshots乳讥。
- 設(shè)置緩存字段 signatures 的大小 inmemorySignatures。
- 需要注意的是廓俭,字段 signer 和 signFn 在初始化時(shí)并未設(shè)置,而是由方法 Authorize() 明確設(shè)置唉工。
(2) func (c *Clique) Author(header *types.Header) (common.Address, error)
方法 Author() 實(shí)現(xiàn)了接口 consensus.Engine研乒,返回區(qū)塊的簽名者地址。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 通過(guò)函數(shù) ecrecover() 返回從區(qū)塊頭額外數(shù)據(jù)部分中的簽名中恢復(fù)的以太坊地址淋硝,即簽名者地址雹熬。
(3) func (c *Clique) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error
方法 VerifyHeader() 檢查在指定的區(qū)塊鏈中區(qū)塊頭是否符合共識(shí)引擎。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 參數(shù) seal 并沒(méi)有被用到
- 具體實(shí)現(xiàn)轉(zhuǎn)發(fā)給同名方法 VerifyHeader
(4) func (c Clique) VerifyHeaders(chain consensus.ChainReader, headers []types.Header, seals []bool) (chan<- struct{}, <-chan error)
方法 VerifyHeaders() 與方法 VerifyHeader() 類似谣膳,但驗(yàn)證了一批區(qū)塊頭竿报。該方法返回退出通道以使調(diào)用者能夠中止此方法中的操作,并返回結(jié)果通道以檢索異步驗(yàn)證(順序是輸入切片的順序)继谚。
主要實(shí)現(xiàn)細(xì)節(jié)如下:
- 參數(shù) seals 并沒(méi)有得到使用烈菌。
- 創(chuàng)建了退出通道 abort,并將其作為返回結(jié)果返回給調(diào)用者。這是個(gè)消息接收通道芽世,表示該通道的消息流是由外向內(nèi)的挚赊,即只能由調(diào)用者發(fā)送消息給此方法。
- 創(chuàng)建了結(jié)果通道 results济瓢,并將其作為返回結(jié)構(gòu)返回給調(diào)用者荠割。這是個(gè)消息發(fā)送通道,表示該通道的消息流是由內(nèi)向外的旺矾,鄧只能由此方法發(fā)送消息給調(diào)用者蔑鹦。
- 啟動(dòng)一個(gè)獨(dú)立的匿名協(xié)程
- 調(diào)用方法 verifyHeader() 依次驗(yàn)證區(qū)塊頭中的區(qū)塊頭
- 持續(xù)監(jiān)聽(tīng)退出通道 abort,如果接收到退出消息箕宙,則中止后續(xù)的驗(yàn)證操作嚎朽。
- 將方法 verifyHeader() 對(duì)于某個(gè)區(qū)塊頭的驗(yàn)證結(jié)果發(fā)送給結(jié)果通道 results。
- 立即返回退出通道 abort 和結(jié)果通道 results扒吁。
對(duì)于返回結(jié)果的兩個(gè)通道需要特別說(shuō)明:
chan<- struct{} 表明只用于接收消息的通道火鼻,且消息體為空的消息,往往只是用來(lái)驅(qū)動(dòng)一個(gè)事件雕崩,如:開(kāi)始操作魁索、中止操作等。
<-chan error 表明只用于發(fā)送消息的通道盼铁。
方法 VerifyHeaders() 的兩個(gè)調(diào)用者:
- BlockChain.insertChain
- HeaderChain.ValidateHeaderChain
(5) func (c *Clique) verifyHeader(chain consensus.ChainReader, header types.Header, parents []types.Header) error
方法 VerifyHeader() 檢查在指定的區(qū)塊鏈中區(qū)塊頭是否符合共識(shí)引擎粗蔚。調(diào)用者可以選擇傳入一批父區(qū)塊(升序),以避免從數(shù)據(jù)庫(kù)中查找這些區(qū)塊饶火。這對(duì)于同時(shí)驗(yàn)證一批新區(qū)塊頭非常有用鹏控。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 獲取區(qū)塊編號(hào) number,如果失敗返回 errUnknownBlock
- 如果區(qū)塊的時(shí)間大于當(dāng)前時(shí)間肤寝,則返回 consensus.ErrFutureBlock
- 重點(diǎn)檢查區(qū)塊頭 types.Header 中的各字段是否滿足 PoA 共識(shí)協(xié)議当辐,如果不滿足則返回對(duì)應(yīng)的錯(cuò)誤消息。
- 對(duì)于處于檢查點(diǎn)位置的區(qū)塊鲤看,字段 Coinbase 應(yīng)該為 common.Address{}缘揪,否則返回 errInvalidCheckpointBeneficiary
- 字段 Nonce 是否為 nonceAuthVote 或 nonceDropVote,否則返回 errInvalidVote
- 對(duì)于處于檢查點(diǎn)位置的區(qū)塊义桂,字段 Nonce 必須為 nonceDropVote找筝,否則返回 errInvalidCheckpointVote
- 字段 Extra 的長(zhǎng)度是否大于等于 extraVanity,否則返回 errMissingVanity
- 字段 Extra 的長(zhǎng)度是否大于等于 extraVanity+extraSeal慷吊,否則返回 errMissingSignature
- 對(duì)于處于非檢查點(diǎn)位置的區(qū)塊袖裕,字段 Extra 中不應(yīng)該包含授權(quán)簽名者列表,即字段 Extra 的字節(jié)數(shù)為 extraVanity+extraSeal溉瓶。否則返回 errExtraSigners
- 對(duì)于處于檢查點(diǎn)位置的區(qū)塊急鳄,字段 Extra 中包含的授權(quán)簽名者列表必須不為空谤民,且授權(quán)簽名者列表占據(jù)的字節(jié)數(shù)為 common.AddressLength 的倍數(shù)。否則返回 errInvalidCheckpointSigners
- 字段 MixDigest 必須為 common.Hash{}攒岛,因?yàn)楫?dāng)前并沒(méi)有 fork 保護(hù)赖临。否則返回 errInvalidMixDigest
- 字段 UncleHash 必須為 uncleHash,因?yàn)樵?PoA 共識(shí)中叔區(qū)塊沒(méi)有意義灾锯。否則返回 errInvalidUncleHash
- 對(duì)于非創(chuàng)始區(qū)塊兢榨,字段 Difficulty 必須是 diffInTurn 或 diffNoTurn,雖然在目前階段可能并不正確顺饮。否則返回 errInvalidDifficulty吵聪。
- 如果上述所有的檢查都通過(guò),則檢查硬分叉相關(guān)的特殊字段兼雄。返回函數(shù) misc.VerifyForkHashes() 的錯(cuò)誤結(jié)果吟逝。
- 如果上述所有的基礎(chǔ)檢查都通過(guò),則檢查級(jí)聯(lián)字段赦肋。返回方法 verifyCascadingFields() 的錯(cuò)誤結(jié)果块攒。
(6) func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header types.Header, parents []types.Header) error
方法 verifyCascadingFields() 驗(yàn)證所有非獨(dú)立的區(qū)塊頭字段,而不是依賴于一批先前的區(qū)塊頭佃乘。調(diào)用者可選地傳遞一批父區(qū)塊(升序)以避免從數(shù)據(jù)庫(kù)中查找這些父區(qū)塊囱井。這對(duì)于同時(shí)驗(yàn)證一批新區(qū)塊頭非常有用。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 獲取區(qū)塊編號(hào) number趣避,如果是創(chuàng)世區(qū)塊則直接返回 nil庞呕。
- 獲取父區(qū)塊頭 parent
- 如果參數(shù) parents 不為空,則 parent = parents[len(parents)-1]
- 否則程帕, parent = chain.GetHeader(header.ParentHash, number-1)
- 對(duì)父區(qū)塊頭進(jìn)行檢查住练,如果檢查失敗返回 consensus.ErrUnknownAncestor。檢查的三個(gè)條件為:
- 父區(qū)塊頭 parent 不為空
- 父區(qū)塊的編號(hào)為當(dāng)前區(qū)塊編號(hào)減 1
- 父區(qū)塊 parnet 的哈希和當(dāng)前區(qū)塊存儲(chǔ)的父區(qū)塊哈希一致愁拭。
- 對(duì)父區(qū)塊的出塊時(shí)間和當(dāng)前區(qū)塊的出塊時(shí)間進(jìn)行判定讲逛,兩者的間隔不能小于出塊的周期 CliqueConfig.Period
- 檢索驗(yàn)證此區(qū)塊頭所需的快照并對(duì)其進(jìn)行緩存。通過(guò)調(diào)用 方法Clique.snapshot() 獲取或構(gòu)建快照 snap岭埠。需要注意的是妆绞,這里的快照 snap 是基于當(dāng)前區(qū)塊上一個(gè)區(qū)塊,因?yàn)閷?duì)于當(dāng)前區(qū)塊的簽名等驗(yàn)證時(shí)枫攀,其授權(quán)簽名者列表是基于上一個(gè)區(qū)塊時(shí)的快照的。
- 對(duì)于處于檢查點(diǎn)位置的區(qū)塊株茶,驗(yàn)證簽名者列表来涨。通過(guò)方法 snap.signers() 返回快照中的有序簽名者列表,并拼接成字節(jié)序列启盛,和區(qū)塊頭 types.Header 中的字段 Extra 中的簽名者列表部分字節(jié)進(jìn)行比較蹦掐,如果不一致技羔,則返回 errInvalidCheckpointSigners
- 如果上述所有的基本檢查都通過(guò)了,則返回方法 Clique.verifySeal() 的驗(yàn)證結(jié)果卧抗。方法 Clique.verifySeal() 主要是驗(yàn)證 PoA 共識(shí)協(xié)議中關(guān)于簽名規(guī)則的那一部分藤滥。
(7) func (c Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []types.Header) (*Snapshot, error)
方法 snapshot() 在給定時(shí)間點(diǎn)檢索授權(quán)快照,如果內(nèi)存緩存和數(shù)據(jù)庫(kù)中都沒(méi)有對(duì)應(yīng)區(qū)塊點(diǎn)的快照社裆,則基于該區(qū)塊點(diǎn)構(gòu)建新的快照拙绊。
主要實(shí)現(xiàn)細(xì)節(jié):
- 參數(shù) number 為區(qū)塊編號(hào)
- 參數(shù) hash 為區(qū)塊哈希
- 參數(shù) parents 為緩存的父區(qū)塊列表。需要注意的時(shí) parents[len(parents)-1] 的區(qū)塊編號(hào)和哈希分別是 number 和 hash泳秀。這是由調(diào)用者傳遞的參數(shù)決定的标沪。
- 定義要傳遞給方法 Snapshot.apply() 的區(qū)塊頭列表 headers, headers []*types.Header
- 定義要返回的快照 snap嗜傅,snap *Snapshot
- 如果 snap == nil金句,則循環(huán)執(zhí)行下列步驟:
- 在內(nèi)存緩存中檢索對(duì)應(yīng)區(qū)塊點(diǎn)的快照。snap = c.recents.Get(hash)
- 在數(shù)據(jù)庫(kù)中檢索對(duì)應(yīng)區(qū)塊點(diǎn)的快照吕嘀,這是因?yàn)槊扛?checkpointInterval 個(gè)區(qū)塊會(huì)把快照歸檔到數(shù)據(jù)庫(kù)违寞。snap = loadSnapshot(c.config, c.signatures, c.db, hash)
- 對(duì)于處于檢查點(diǎn)的區(qū)塊,并且該區(qū)塊的父區(qū)塊存在(表示該區(qū)塊基本在區(qū)塊鏈上)偶房,先從當(dāng)前區(qū)塊中收集簽名者列表趁曼,再作為參數(shù)調(diào)用函數(shù) newSnapshot() 創(chuàng)建一個(gè)新的快照 snap,并將快照 snap 歸檔到數(shù)據(jù)庫(kù)蝴悉。但是這一步保存的快照為檢查點(diǎn)快照彰阴,里面沒(méi)有任何投票信息。這一步會(huì)輸出一個(gè)非常重要的日志信息拍冠,歸檔了檢查點(diǎn)快照尿这, log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)。
- 如果對(duì)于當(dāng)前區(qū)塊不存在快照庆杜,則首先在參數(shù) parents 中查找父區(qū)塊頭 header射众,并對(duì)參數(shù) hash 和 number 進(jìn)行強(qiáng)制檢查,檢查失敗返回 consensus.ErrUnknownAncestor晃财。如果參數(shù) parents 不存在叨橱,則到數(shù)據(jù)庫(kù)去查找,如果沒(méi)找到断盛,也返回 consensus.ErrUnknownAncestor罗洗。
- 并父區(qū)塊頭加入 headers 中,之后調(diào)用 Snapshot.apply(headers) 方法钢猛,將這些對(duì)應(yīng)區(qū)塊列表還沒(méi)有快照的創(chuàng)建對(duì)應(yīng)的快照伙菜。
- 更新 number 和 hash,進(jìn)行下一輪循環(huán)迭代命迈。
- 找到上一個(gè)快照贩绕,在其上通過(guò)方法 apply(headers) 應(yīng)用任何待處理的區(qū)塊頭火的。這里需要對(duì)前面計(jì)算出的 headers 做一個(gè)處理,因?yàn)榍懊娴?headers 是根據(jù)區(qū)塊編號(hào)倒序排列的淑倾,而方法 apply(headers) 需要 headers 中的區(qū)塊編號(hào)為升序的馏鹤。
- 將新創(chuàng)建的快照加入緩存 recents 中,c.recents.Add(snap.Hash, snap)
- 如果我們創(chuàng)建了新的檢查點(diǎn)快照娇哆,并且正好也滿足歸檔的頻率 checkpointInterval湃累,則把此快照存入數(shù)據(jù)庫(kù)。這里需要注意迂尝,這一步存儲(chǔ)的快照是記錄投票信息的脱茉,因此得避免基于創(chuàng)世區(qū)塊檢查點(diǎn)的快照,因?yàn)閯?chuàng)世區(qū)塊檢查點(diǎn)的快照沒(méi)有任何投票記錄垄开。 這一步會(huì)生成非常重要的日志信息琴许,歸檔了投票快照,log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
??? 問(wèn)題:這里面有兩種類型的快照溉躲,一種是處于檢查點(diǎn)位置但不包含任何投票記錄的快照榜田,一種是處理檢查點(diǎn)位置且包含這一輪投票記錄的快照。兩者頻率各為 c.config.Epoch 和 checkpointInterval锻梳,還是前者的頻率為 c.config.Epoch箭券,后者的頻率為 checkpointInterval * c.config.Epoch,還是說(shuō)每個(gè)區(qū)塊都會(huì)有快照疑枯?
結(jié)果應(yīng)該是:兩者頻率各為 c.config.Epoch 和 checkpointInterval辩块。每隔間隔 c.config.Epoch 個(gè)區(qū)塊創(chuàng)建新的快照,用于基于此檢查點(diǎn)的快照驗(yàn)證授權(quán)簽名者列表荆永。記錄檢查點(diǎn)快照日志 log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)废亭。然后每隔一定數(shù)量的區(qū)塊應(yīng)用一次當(dāng)前區(qū)塊到上一次檢查點(diǎn)快照或上一次投票快照之間所有區(qū)塊的投票,生成新的投票快照點(diǎn)具钥。這個(gè)數(shù)量具體是多少豆村?然后投票快照基于的區(qū)塊正好間隔 checkpointInterval 個(gè)區(qū)塊,則歸檔骂删,并記錄日志 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)掌动。
對(duì)于創(chuàng)世區(qū)塊,它只有檢查點(diǎn)快照宁玫,不會(huì)有投票記錄快照粗恢,因?yàn)橹皼](méi)有任何投票記錄。
也就是說(shuō)后續(xù)的區(qū)塊驗(yàn)證只從上一次檢查點(diǎn)的區(qū)塊和快照開(kāi)始欧瘪,并不需要依次驗(yàn)證到創(chuàng)世區(qū)塊适滓,只需要驗(yàn)證到上一個(gè)檢查點(diǎn)區(qū)塊即可。然后會(huì)有間隔地在上一次檢查點(diǎn)到當(dāng)前某一個(gè)區(qū)塊,將此之間的所有區(qū)塊的投票結(jié)果實(shí)際上產(chǎn)生實(shí)際的作用凭迹,用來(lái)更新授權(quán)簽名者列表。
??? 問(wèn)題苦囱,現(xiàn)在不知道在上一次檢查點(diǎn)到當(dāng)前區(qū)塊之間應(yīng)該是間隔多少個(gè)區(qū)塊嗅绸,進(jìn)行一次投票生效撕彤。每隔 K/2 + 1 個(gè)區(qū)塊鱼鸠? K 是上一個(gè)檢查點(diǎn)區(qū)塊中的授權(quán)簽名者列表的長(zhǎng)度「Γ肯定不是每個(gè)區(qū)塊產(chǎn)生一個(gè)快照蚀狰。而且,可以每 c.config.Epoch 個(gè)區(qū)塊新建一個(gè)檢查點(diǎn)區(qū)塊并進(jìn)行歸檔职员。在檢查點(diǎn)區(qū)塊之后麻蹋,每隔多少個(gè)區(qū)塊頭產(chǎn)生一次投票生效點(diǎn)快照?而且如果投票生效點(diǎn)快照正好是 checkpointInterval 的倍數(shù)焊切,會(huì)將投票生效點(diǎn)快照進(jìn)行歸檔扮授。
(8) func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) error
方法 VerifyUncles() 實(shí)現(xiàn)了接口 consensus.Engine,總是為任何叔區(qū)塊返回錯(cuò)誤专肪,因?yàn)檫@種共識(shí)機(jī)制不允許有叔區(qū)塊刹勃。
主要實(shí)現(xiàn)細(xì)節(jié):
- 判斷 block.Uncles 的長(zhǎng)度是否大于 0.
(9) func (c *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) error
方法 VerifySeal() 實(shí)現(xiàn)了接口 consensus.Engine,檢查包含在區(qū)塊頭中的簽名是否符合 PoA 共識(shí)協(xié)議的需求嚎尤。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 將具體實(shí)現(xiàn)轉(zhuǎn)發(fā)給方法 Clique.verifySeal()
方法 VerifySeal() 主要是用于驗(yàn)證區(qū)塊頭中關(guān)于簽名的那部分信息荔仁,區(qū)塊頭中的其它信息由 VerifyHeader() 方法族實(shí)現(xiàn)。
(10) func (c *Clique) verifySeal(chain consensus.ChainReader, header types.Header, parents []types.Header) error
方法 verifySeal() 檢查包含在區(qū)塊頭中的簽名是否符合 PoA 共識(shí)協(xié)議的需求芽死。此方法接受可選的父區(qū)塊頭列表乏梁,這些父區(qū)塊頭列表還不是本地區(qū)塊鏈的一部分,快照就是從這些父區(qū)塊頭列表中生成收奔。
主要的實(shí)現(xiàn)細(xì)節(jié):
- 獲取區(qū)塊編號(hào) number掌呜,如果是創(chuàng)世區(qū)塊,則返回 errUnknownBlock坪哄。因?yàn)椴恢С烛?yàn)證創(chuàng)世區(qū)塊的簽名质蕉,雖然創(chuàng)世區(qū)塊中包含最初的授權(quán)簽名者列表。
- 檢索驗(yàn)證此區(qū)塊頭所需的快照 snap翩肌,并緩存快照模暗。主要實(shí)現(xiàn)代碼為 snap = c.snapshot(chain, number-1, header.ParentHash, parents)
- 恢復(fù)出簽名者 signer,主要實(shí)現(xiàn)代碼為 signer = ecrecover(header, c.signatures)
- 如果 snap.Signers[signer] 不存在念祭,則返回 errUnauthorized兑宇。
- 檢查 snap.Recents,如果 signer 在 snap.Recents 中的最近 K/2 + 1 個(gè)區(qū)塊內(nèi)進(jìn)行過(guò)簽名粱坤,則返回 errUnauthorized隶糕。
- 如果簽名為 inturn瓷产,檢查對(duì)應(yīng)的難度是否為 diffInTurn,如果不是則返回 errInvalidDifficulty枚驻。
- 如果簽名不為 inturn濒旦,檢查對(duì)應(yīng)的難度是否為 diffNoTurn,如果不是則返回 errInvalidDifficulty再登。
(11) func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) error
方法 Prepare() 實(shí)現(xiàn)了接口 consensus.Engine尔邓,為在區(qū)塊頭上運(yùn)行事務(wù),準(zhǔn)備其所需的共識(shí)字段锉矢。
主要實(shí)現(xiàn)細(xì)節(jié)如下:
- 如果非檢查點(diǎn)區(qū)塊梯嗽,做一隨機(jī)投票,或者說(shuō)默認(rèn)投票(目前為止沒(méi)有問(wèn)題)沽损。代碼為:
- header.Coinbase = common.Address{}
- header.Nonce = types.BlockNonce{}
- 獲取區(qū)塊編號(hào) number
- 組裝投票快照以檢查哪些投票能夠生效灯节,即獲取父區(qū)塊那個(gè)位置的快照 snap
- 對(duì)于非檢查點(diǎn)區(qū)塊,嘗試進(jìn)行提議
- 加鎖缠俺,代碼為 c.lock.RLock()
- 從提議列表中隨機(jī)獲取一個(gè)提議显晶,非有效提議已經(jīng)被方法 snap.validVote() 進(jìn)行了過(guò)濾。用該提議的賬戶地址填充 header.Coinbase壹士。同時(shí)磷雇,根據(jù)該提議是對(duì)賬戶地址進(jìn)行授權(quán)投票還是解除授權(quán)投票,分別填充 header.Nonce 為 nonceAuthVote 和 nonceDropVote躏救。
- 解鎖唯笙,代碼為 c.lock.RUnlock()
- 設(shè)置正確的區(qū)塊難度,代碼為 header.Difficulty = CalcDifficulty(snap, c.signer)。具體的難度計(jì)算規(guī)則參考方法 CalcDifficulty()
- 確保字段 Extra 的三部分都得到正確填充
- header.Extra[:extraVanity] 這部分根據(jù)需要補(bǔ)充為 extraVanity 個(gè)字節(jié)
- 如果是檢查點(diǎn)區(qū)塊,則將升序的授權(quán)簽名者列表(通過(guò)方法 snap.signers() 獲得)添加到 header.Extra[extraVanity] 之后
- 在授權(quán)簽名者列表信息之后補(bǔ)充 extraSeal 個(gè)字節(jié)的默認(rèn)值屋匕,用于之后修正為簽名介劫。
- 字段 MixDigest 目前為保留的曹鸠,設(shè)置為空。代碼為 header.MixDigest = common.Hash{}
- 確保當(dāng)前區(qū)塊和父區(qū)塊之間有正確的時(shí)間延遲,即設(shè)置正確的字段 header.Time。
- 先獲取父區(qū)塊頭 parent挽放,如果為 nil 則返回 consensus.ErrUnknownAncestor。代碼為 parent := chain.GetHeader(header.ParentHash, number-1);
if parent == nil {
return consensus.ErrUnknownAncestor
} - 調(diào)整 header.Time 和父區(qū)塊之間有正確的時(shí)間延遲蔓纠。代碼為 header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period));
if header.Time.Int64() < time.Now().Unix() {
header.Time = big.NewInt(time.Now().Unix())
}
- 先獲取父區(qū)塊頭 parent挽放,如果為 nil 則返回 consensus.ErrUnknownAncestor。代碼為 parent := chain.GetHeader(header.ParentHash, number-1);
(12) func (c Clique) Finalize(chain consensus.ChainReader, header types.Header, state state.StateDB, txs []types.Transaction, uncles []types.Header, receipts []types.Receipt) (*types.Block, error)
方法 Finalize() 實(shí)現(xiàn)了接口 consensus.Engine辑畦,確保沒(méi)有叔區(qū)塊被設(shè)置,沒(méi)有區(qū)塊獎(jiǎng)勵(lì)被設(shè)置腿倚,并返回最終的區(qū)塊纯出。這個(gè)區(qū)塊的三棵樹(shù)(狀態(tài)樹(shù)、事務(wù)樹(shù)、收據(jù)樹(shù))都已經(jīng)被確定下來(lái)了暂筝,但是區(qū)塊頭中關(guān)于事務(wù)樹(shù)箩言、收據(jù)樹(shù)的根哈希應(yīng)該還沒(méi)有被確定下來(lái),特別是還沒(méi)有進(jìn)行簽名焕襟,簽名信息應(yīng)該是在方法 Clique.Seal() 方法之后才被確定下來(lái)的分扎。同時(shí)叔區(qū)塊列表也被確定為空,且區(qū)塊頭中對(duì)應(yīng)的叔區(qū)塊哈希也被確定胧洒。
主要實(shí)現(xiàn)細(xì)節(jié):
- 確定狀態(tài)樹(shù)的根哈希 header.Root,代碼為 header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
- 確定叔區(qū)塊列表的哈希header.UncleHash墨状,代碼為 header.UncleHash = types.CalcUncleHash(nil)
- 組裝并返回最終可以被簽名的區(qū)塊卫漫,代碼為 return types.NewBlock(header, txs, nil, receipts)
(13) func (c *Clique) Authorize(signer common.Address, signFn SignerFn)
方法 Authorize() 設(shè)置共識(shí)引擎的簽名者和簽名函數(shù)。
主要實(shí)現(xiàn)細(xì)節(jié):
- 加鎖肾砂,代碼為 c.lock.Lock()
- 方法結(jié)束時(shí)解鎖列赎,代碼為 defer c.lock.Unlock()
- 設(shè)置共識(shí)引擎的簽名者,代碼為 c.signer = signer
- 設(shè)置共識(shí)引擎的簽名函數(shù)镐确,代碼為 c.signFn = signFn
(14) func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
方法 Seal() 實(shí)現(xiàn)了接口 consensus.Engine包吝,嘗試使用本地簽名標(biāo)準(zhǔn)創(chuàng)建一個(gè)被簽名的區(qū)塊。
對(duì)于參數(shù)中指定的待簽名區(qū)塊 block源葫,嘗試對(duì)其進(jìn)行簽名诗越,如果簽名成功則將簽名后的區(qū)塊發(fā)送到參數(shù)結(jié)果通道 results,結(jié)果通道 results 用于接收被簽名過(guò)的區(qū)塊息堂,這個(gè)區(qū)塊將作為本地最新挖出的區(qū)塊被添加進(jìn)待網(wǎng)絡(luò)中其它節(jié)點(diǎn)確認(rèn)的節(jié)點(diǎn)列表中嚷狞。
在簽名過(guò)程中,如果出現(xiàn)錯(cuò)誤荣堰,將發(fā)送中止消息給參數(shù)退出通道 stop床未,用于中止調(diào)用者的操作。參數(shù)退出通道 stop 是調(diào)用者指定的退出通道振坚,用于接收被調(diào)用者的出錯(cuò)消息薇搁,從而中止后續(xù)操作。
主要實(shí)現(xiàn)如下:
- 獲取區(qū)塊頭 header 用于后續(xù)的簽名渡八。代碼為 header := block.Header()
- 獲取區(qū)塊編號(hào) number啃洋,如果是創(chuàng)世區(qū)塊則返回 errUnknownBlock,這是因?yàn)椴恢С謱?duì)創(chuàng)始區(qū)塊進(jìn)行簽名呀狼。
- 對(duì)于 0-period 的區(qū)塊鏈裂允,拒絕簽名空區(qū)塊(沒(méi)有獎(jiǎng)勵(lì),但會(huì)自旋簽名)哥艇,返回 errWaitTransactions绝编。即如果 c.config.Period == 0,則區(qū)塊中的事務(wù)不能為空。
- 不要在整個(gè)簽名過(guò)程中持有簽名者字段十饥。代碼為 c.lock.RLock(); signer, signFn := c.signer, c.signFn; c.lock.RUnlock()
- 檢索到上一個(gè)區(qū)塊時(shí)的快照窟勃,代碼為 snap = c.snapshot(chain, number-1, header.ParentHash, nil)
- 檢查當(dāng)前共識(shí)引擎的簽名者 signer 是否為有效的簽名者,即檢查 snap.Signers[signer] 是否存在逗堵,如果不存在則返回 errUnauthorized
- 如果簽名者 signer 在 snap.Recents 的最近 K/2 + 1 個(gè)區(qū)塊中簽名過(guò)秉氧,則直接退出此次簽名過(guò)程,將簽名機(jī)會(huì)留給其它簽名者(即網(wǎng)絡(luò)中的其它礦工)蜒秤。其中汁咏,K 為授權(quán)簽名者列表 Snapshot.Signers 的長(zhǎng)度。這一步會(huì)輸出重要的日志信息 log.Info("Signed recently, must wait for others")
- 到此作媚,共識(shí)協(xié)議允許我們簽署區(qū)塊攘滩,隨機(jī)一個(gè)只屬于我們的延遲簽名時(shí)間 delay,這樣是為了使得網(wǎng)絡(luò)中各個(gè)簽名者實(shí)際的簽名時(shí)間有略微的差別纸泡。delay 的具體計(jì)算請(qǐng)參考代碼漂问。
- 使用簽名者 singer 作為 Key,對(duì)區(qū)塊簽名哈希進(jìn)行簽名女揭,并獲得簽名 sighash蚤假,代碼為 sighash = signFn(accounts.Account{Address: signer}, sigHash(header).Bytes())
- 將簽名 sighash 替換區(qū)塊頭字段 Extra 中的最后 65 個(gè)字節(jié),代碼為 copy(header.Extra[len(header.Extra)-extraSeal:], sighash)
- 啟動(dòng)一個(gè)獨(dú)立的匿名協(xié)程吧兔,等待簽名完成或者延遲超時(shí)磷仰。這一步會(huì)輸出重要的日志信息 log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay))
- 在退出通道 stop 和 Time 通道進(jìn)行持續(xù)監(jiān)聽(tīng)
- 如果從退出通道 stop 接收到中止消息,則直接中止協(xié)程掩驱。
- 如果在 dalay 之后從 Time 通道接收到消息芒划,則繼續(xù)后續(xù)的流程
- 用簽名后的區(qū)塊頭替換原區(qū)塊,并將帶有簽名的區(qū)塊發(fā)送給結(jié)果通道 results欧穴,這個(gè)區(qū)塊將作為本地最新挖出的區(qū)塊被添加進(jìn)待網(wǎng)絡(luò)中其它節(jié)點(diǎn)確認(rèn)的節(jié)點(diǎn)列表中民逼。。見(jiàn)代碼 results <- block.WithSeal(header):
- 在退出通道 stop 和 Time 通道進(jìn)行持續(xù)監(jiān)聽(tīng)
(15) func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int
方法 CalcDifficulty() 是難度調(diào)整算法涮帘。它根據(jù)鏈中的先前區(qū)塊和當(dāng)前簽名者返回新區(qū)塊應(yīng)具有的難度拼苍。
主要實(shí)現(xiàn)如下:
- 檢索快照 snap,代碼為 snap= c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
- 將具體的計(jì)算轉(zhuǎn)發(fā)給函數(shù) CalcDifficulty()调缨,代碼為 return CalcDifficulty(snap, c.signer)
(16) func (c *Clique) SealHash(header *types.Header) common.Hash
方法 SealHash() 返回區(qū)塊在被簽名之前的哈希值疮鲫,即返回區(qū)塊簽名哈希。
主要實(shí)現(xiàn):
- 將具體的計(jì)算轉(zhuǎn)發(fā)給函數(shù) sigHash弦叶,代碼為 sigHash(header)
(17) func (c *Clique) Close() error
方法 Close() 實(shí)現(xiàn)了接口 consensus.Engine俊犯。由于沒(méi)有后端線程,這對(duì) Clique 共識(shí)引擎來(lái)說(shuō)是一個(gè) noop 操作伤哺。
主要實(shí)現(xiàn):
- return nil
(18) func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API
方法 APIs() 實(shí)現(xiàn)了接口 consensus.Engine燕侠,給用戶返回 RPC API 以允許其控制簽名者投票者祖。
如:
- 對(duì)賬戶 address 的授權(quán)投票 clique.propose(address, true)
- 對(duì)賬戶 address 的解除授權(quán)投票 clique.propose(address, false)
其中,address 為賬戶地址绢彤。
8. func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int
函數(shù) CalcDifficulty() 是難度調(diào)整算法七问。它根據(jù)鏈中的先前區(qū)塊和當(dāng)前簽名者返回新區(qū)塊應(yīng)具有的難度。
主要實(shí)現(xiàn):
- 如果簽名者 signer 在快照中的簽名為 inturn茫舶,則難度為 diffInTurn械巡,否則難度為 diffNoTurn。
Reference
Contributor
- Windstamp, https://github.com/windstamp