摘要:以太坊中除了基于運算能力的外,還有基于權利證明的共識機制,是以太坊的共識算法的實現,這里主要對的相關源碼做一個解讀分析。檢查包頭中包含的簽名是否滿足共識協議
以太坊中除了基于運算能力的POW(Ethash)外,還有基于權利證明的POA共識機制,Clique是以太坊的POA共識算法的實現,這里主要對POA的Clique相關源碼做一個解讀分析。
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置為clique.Clique, 根據當前節點的礦工地址(默認是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash) ,其中簽名函數是SignHash,對給定的hash進行簽名。
func (s *Ethereum) StartMining(local bool) error { eb, err := s.Etherbase()//用戶地址 if err != nil { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } if clique, ok := s.engine.(*clique.Clique); ok { //如果是clique共識算法 wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根據用它胡地址獲取wallet對象 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) // 注入簽名者以及wallet對象獲取簽名方法 } if local { // 如果本地CPU已開始挖礦,我們可以禁用引入的交易拒絕機制來加速同步時間。CPU挖礦在主網是荒誕的,所以沒有人能碰到這個路徑,然而一旦CPU挖礦同步標志完成以后,將保證私網工作也在一個獨立礦工結點。 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1) } go s.miner.Start(eb) return nil }
這個StartMining會在miner.start前調用,然后通過woker -> agent -> CPUAgent -> update -> seal 挖掘區塊和組裝(后面會寫多帶帶的文章來對挖礦過程做源碼分析)。
Clique的代碼塊在go-ethereum/consensus/clique路徑下。和ethash一樣,在clique.go 中實現了consensus的接口, consensus 定義了下面這些接口:
type Engine interface { Author(header *types.Header) (common.Address, error) VerifyHeader(chain ChainReader, header *types.Header, seal bool) error VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) VerifyUncles(chain ChainReader, block *types.Block) error VerifySeal(chain ChainReader, header *types.Header) error Prepare(chain ChainReader, header *types.Header) error Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int APIs(chain ChainReader) []rpc.API }
Engine.Seal()函數可對一個調用過 Finalize()的區塊進行授權或封印,成功時返回的區塊全部成員齊整,可視為一個正常區塊,可被廣播到整個網絡中,也可以被插入區塊鏈等。對于挖掘一個新區塊來說,所有相關代碼里 Engine.Seal()是其中最重要最復雜的一步,所以這里我們首先來看下Clique 結構體:
type Clique struct { config *params.CliqueConfig // 共識引擎配置參數 db ethdb.Database // 數據庫,用來存儲和獲取快照檢查點 recents *lru.ARCCache // 最近區塊快照,加速快照重組 signatures *lru.ARCCache // 最近區塊簽名,加速挖礦 proposals map[common.Address]bool // 目前正在推送的提案 signer common.Address // 簽名者的以太坊地址 signFn SignerFn // 授權哈希的簽名方法 lock sync.RWMutex // 用鎖來保護簽名字段 }
順便來看下CliqueConfig共識引擎的配置參數結構體:
type CliqueConfig struct { Period uint64 `json:"period"` // 在區塊之間執行的秒數(比如出塊秒數15s) Epoch uint64 `json:"epoch"` // Epoch長度,重置投票和檢查點(比如Epoch長度是30000個block, 每次進入新的epoch,前面的投票都被清空, 重新開始記錄) }
在上面的 StartMining中,通過Clique. Authorize來注入簽名者和簽名方法,先來看下Authorize:
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) { c.lock.Lock() defer c.lock.Unlock() // 這個方法就是為clique共識注入一個簽名者的私鑰地址已經簽名函數用來挖出新塊 c.signer = signer c.signFn = signFn }
再來看Clique的Seal()函數的具體實現:
//通過本地簽名認證創建已密封的區塊 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 { return nil, errUnknownBlock } // 不支持0-period的鏈,不支持空塊密封,沒有獎勵但是能夠密封 if c.config.Period == 0 && len(block.Transactions()) == 0 { return nil, errWaitTransactions } // 在整個密封區塊的過程中不要持有signer簽名者字段 c.lock.RLock() signer, signFn := c.signer, c.signFn //獲取簽名者和簽名方法 c.lock.RUnlock() snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) //調用獲取快照 if err != nil { return nil, err } //檢查我們是否被授權去簽名一個區塊 if _, authorized := snap.Signers[signer]; !authorized { return nil, errUnauthorized } // 如果我們是在‘最近簽名者’中則等待下一個區塊 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 } } } // 好了,走到這說明協議已經允許我們來簽名這個區塊,等待我們的時間 delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple if header.Difficulty.Cmp(diffNoTurn) == 0 { // 這不是我們的輪次來簽名,延遲一點,隨機延遲,這樣對于每一個簽簽名者來說來允許并發簽名 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)) } log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) select { case <-stop: return nil, nil case <-time.After(delay): } // 通過signFn簽名函數開始簽名 sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) if err != nil { return nil, err } //將簽名結果替換保存在區塊頭的Extra字段中 copy(header.Extra[len(header.Extra)-extraSeal:], sighash) //通過區塊頭重新組裝生成一個區塊 return block.WithSeal(header), nil }
Seal是共識引擎的入口之一,該函數通過clique.signer對區塊簽名
signer不在snapshot的signer中不允許簽名
signer不是本區塊的簽名者需要延時隨機一段時候后再簽名,是本區塊的簽名者則直接簽名
簽名存放在Extra的extraSeal的65個字節中
關于機會均等
為了使得出塊的負載(或者說是機會)對于每個認證節點盡量均等,同時避免某些惡意節點持續出塊,clique中規定每一個認證節點在連續SIGNER_LIMIT個區塊中,最多只能簽發一個區塊,也就是說,每一輪中,最多只有SIGNER_COUNT - SIGNER_LIMIT個認證節點可以參與區塊簽發。
其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示認證節點的個數。
//snap.Signers是所有的認證節點 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 } } }
在保證好節點的個數大于壞節點的前提下,好節點最少的個數為SIGNER_LIMIT(大于50%),壞節點最多的個數為SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一個節點在SIGNER_LIMIT這個時間窗口內最多只能簽發一個區塊,這就使得惡意節點在不超過50%的情況下,從理論上無法一直掌握區塊的簽發權。
關于難度計算
為了讓每個認證節點都有均等的機會去簽發一個區塊,每個節點在簽發時都會判斷本節點是不是本輪的inturn節點,若是inturn節點,則該節點產生的區塊難度為2,否則為1。每一輪僅有一個節點為inturn節點。
diffInTurn = big.NewInt(2) diffNoTurn = big.NewInt(1)
當inturn的結點離線時,其他結點會來競爭,難度值降為1。然而正常出塊時,limit中的所有認證結點包括一個inturn和其他noturn的結點,clique是采用了給noturn加延遲時間的方式來支持inturn首先出塊,避免noturn的結點無謂生成區塊,上面的延時代碼段已經有提現了。
判斷是否為inturn的節點,將本地維護的認證節點按照字典序排序,若當前區塊號除以認證節點個數的余數等于該節點的下標,則該節點為inturn節點。代碼實現在 snapshot.go中:
// 通過給定的區塊高度和簽發者返回該簽發者是否在輪次內 func (s *Snapshot) inturn(number uint64, signer common.Address) bool { signers, offset := s.signers(), 0 for offset < len(signers) && signers[offset] != signer { offset++ } return (number % uint64(len(signers))) == uint64(offset) }
Seal()代碼中有獲取快照,然后從快照中來檢查授權區塊簽名者的邏輯,那么我們繼續來看下Snapshot,首先看下Snapshot的結構體:
// Snapshot對象是在給定時間點的一個認證投票的狀態 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"` // 當下認證簽名者的列表 Recents map[uint64]common.Address `json:"recents"` // 最近擔當過數字簽名算法的signer 的地址 Votes []*Vote `json:"votes"` // 按時間順序排列的投票名單。 Tally map[common.Address]Tally `json:"tally"` // 當前的投票結果,避免重新計算。 }
快照Snapshot對象中存在投票的Votes和記票的Tally對象:
// Vote代表了一個獨立的投票,這個投票可以授權一個簽名者,更改授權列表。 type Vote struct { Signer common.Address `json:"signer"` // 已授權的簽名者(通過投票) Block uint64 `json:"block"` // 投票區塊號 Address common.Address `json:"address"` // 被投票的賬戶,修改它的授權 Authorize bool `json:"authorize"` // 對一個被投票賬戶是否授權或解授權 } // Tally是一個簡單的用來保存當前投票分數的計分器 type Tally struct { Authorize bool `json:"authorize"` // 授權true或移除false Votes int `json:"votes"` // 該提案已獲票數 }
Snapshot是一個快照,不僅是一個緩存,而且存儲了最近簽名者的map
loadSnapshot用來從數據庫中加載一個已存在的快照:
func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { //使用Database接口的Get方法通過Key來查詢緩存內容 blob, err := db.Get(append([]byte("clique-"), hash[:]...)) 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 }
newSnapshot函數用于創建快照,這個方法沒有初始化最近的簽名者集合,所以只使用創世塊:
func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { //組裝一個Snapshot對象 snap := &Snapshot{ 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 }
繼續看下snapshot函數的具體實現:
// 快照會在給定的時間點檢索授權快照 func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { // 在內存或者磁盤上查找一個快照來檢查檢查點checkpoints var ( headers []*types.Header //區塊頭 snap *Snapshot //快照對象 ) for snap == nil { // 如果在內存中找到快照時,快照對象從內存中取 if s, ok := c.recents.Get(hash); ok { snap = s.(*Snapshot) break } // 如果在磁盤檢查點找到快照時 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到數據庫的區塊的區塊號 if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash) snap = s break } } // 如果在創世塊,則新建一個快照 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) if err := snap.store(c.db); err != nil { return nil, err } log.Trace("Stored genesis voting snapshot to disk") break } // 沒有對于這個區塊頭的快照,收集區塊頭并向后移 var header *types.Header if len(parents) > 0 { // 如果我們有明確的父,從那里挑選(強制執行) header = parents[len(parents)-1] if header.Hash() != hash || header.Number.Uint64() != number { return nil, consensus.ErrUnknownAncestor } parents = parents[:len(parents)-1] } else { // 沒有明確的父(或者沒有更多的父)轉到數據庫獲取 header = chain.GetHeader(hash, number) if header == nil { return nil, consensus.ErrUnknownAncestor } } headers = append(headers, header) number, hash = number-1, header.ParentHash } // 找到了之前的快照,將所有的pedding塊頭放在它上面 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) //將當前區塊的區塊hash保存到最近區塊快照,加速快照重組 // 如果我們已經生成一個新的檢查點快照,保存在磁盤上 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中,snap.apply通過區塊頭來創建一個新的快照,這個apply中主要做什么操作?
//apply將給定的區塊頭應用于原始頭來創建新的授權快照。 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() // 投票的處理核心代碼 for _, header := range headers { // 刪除檢查點區塊的所有投票 number := header.Number.Uint64() // 如果區塊高度正好在Epoch結束,則清空投票和計分器,避免了維護統計信息無限增大的內存開銷; 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) } // 從區塊頭中解密出來簽名者地址 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 // 區塊頭認證,不管該簽名者之前的任何投票 for i, vote := range snap.Votes { if vote.Signer == signer && vote.Address == header.Coinbase { // 從緩存計數器中移除該投票 snap.uncast(vote.Address, vote.Authorize) // 從按時間排序的列表中移除投票 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) break // 只允許一票 } } // 從簽名者中計數新的投票 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, }) } // 判斷票數是否超過一半的投票者,如果投票通過,更新簽名者列表 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-- } } } // 不管之前的任何投票,直接改變賬戶 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 }
Snapshot.apply()方法的主要部分是迭代處理每個header對象,首先從數字簽名中恢復出簽名所用公鑰,轉化為common.Address類型,作為signer地址。數字簽名(signagure)長度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未認證的,則直接退出本次迭代;如果是已認證的,則投票+1。所以一個父區塊可添加一張記名投票,signer作為投票方地址,Header.Coinbase作為被投票地址,投票內容authorized可由Header.Nonce取值確定。更新投票統計信息。如果被投票地址的總投票次數達到已認證地址個數的一半,則通過之。該被投票地址的認證狀態立即被更改,根據是何種更改,相應的更新緩存數據,并刪除過時的投票信息。在所有Header對象都被處理完后,Snapshot內部的Number,Hash值會被更新,表明當前Snapshot快照結構已經更新到哪個區塊了。
區塊驗證的過程是普通節點在收到一個新區塊時,會從區塊頭的extraData字段中取出認證節點的簽名,利用標準的spec256k1橢圓曲線進行反解公鑰信息,并且從公鑰中截取出簽發節點的地址,若該節點是認證節點,且該節點本輪擁有簽名的權限,則認為該區塊為合法區塊。verifySeal是被SubmitWork(miner/remote_agent.go) 來調用,SubmitWork函數嘗試注入一個pow解決方案(共識引擎)到遠程代理,返回這個解決方案是否被接受。(不能同時是一個壞的pow也不能有其他任何錯誤,例如沒有工作被pending)解決方案有效時,返回到礦工并且通知接受結果。
// 檢查包頭中包含的簽名是否滿足共識協議要求。該方法接受一個可選的父頭的列表,這些父頭還不是本地區塊鏈的一部分,用于生成快照 func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { // 不支持校檢創世塊 number := header.Number.Uint64() if number == 0 { return errUnknownBlock } // 檢索出所需的區塊對象來校檢去開頭和將其緩存 snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { return err } //解析授權密鑰并檢查簽署者,ecrecover方法從區塊頭中反解出Extra字段中簽名字符串來獲取簽名者地址 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 { // 簽署者是最近的,只有當前塊沒有移出時才會失敗,參見seal中的機會均等 if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { return errUnauthorized } } } // 設置區塊難度,參見上面的區塊難度部分 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 }
前面已經分析了Clique的認證節點的出塊和校檢的過程,那么如何來區分一個節點是認證節點還是一個普通節點?以及一個授權者列表是如何產生并如何全網同步的?
Clique通過投票機制來確認一個認證節點,投票的范圍在委員會中,委員會就是所有節點礦工集合,普通節點沒有區塊生成權利。礦工的投票流程如下:
委員會節點通過RPC調用Propose,對某節點狀態變更,從普通節點變成認證階段,或者相反,寫入到Clique.purposal集合中
// Propose注入一個新的授權提案,可以授權一個簽名者或者移除一個。 func (api *API) Propose(address common.Address, auth bool) { api.clique.lock.Lock() defer api.clique.lock.Unlock() api.clique.proposals[address] = auth// true:授權,false:移除 }
本地認證節點在一次區塊打包的過程中,從purposal池中隨機挑選一條還未被應用的purposal,并將信息填入區塊頭,將區塊廣播給其他節點;
//Clique.Prepare // 抓取所有有意義投票的提案 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))] //隨機挑選一條投票節點的地址賦值給區塊頭的Coinbase字段。 // 通過提案內容來組裝區塊頭的隨機數字段。 if c.proposals[header.Coinbase] { copy(header.Nonce[:], nonceAuthVote) } else { copy(header.Nonce[:], nonceDropVote) } }
在挖礦開始以后,會在miner.start()中提交一個commitNewWork,其中調用上面Prepare
if err := self.engine.Prepare(self.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return }
其他節點在接收到區塊后,取出其中的信息,封裝成一個vote進行存儲,并將投票結果應用到本地,若關于目標節點的狀態更改獲得的一致投票超過1/2,則更改目標節點的狀態:若為新增認證節點,將目標節點的地址添加到本地的認證節點的列表中;若為刪除認證節點,將目標節點的地址從本地的認證節點列表中刪除。具體實現可以查看上面的Snapshot.apply()方法
轉載請注明: 轉載自Ryan是菜鳥 | LNMP技術棧筆記
如果覺得本篇文章對您十分有益,何不 打賞一下
本文鏈接地址: 以太坊POA共識機制Clique源碼分析
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24032.html
摘要:前言是以太坊封定義的一個接口,它的功能可以分為類驗證區塊類,主要用在將區塊加入到區塊鏈前,對區塊進行共識驗證。輔助類生成以太坊共識相關的。被使用,是以太坊狀態管理服務,當報告數據的時候,需要獲取區塊的信息。 前言 engine是以太坊封定義的一個接口,它的功能可以分為3類: 驗證區塊類,主要用在將區塊加入到區塊鏈前,對區塊進行共識驗證。 產生區塊類,主要用在挖礦時。 輔助類。 接下...
摘要:本文首發于深入淺出區塊鏈社區原文鏈接以太坊創世區塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。以太坊允許通過創世配置文件來初始化創世區塊,也可使用選擇使用內置的多個網絡環境的創世配置。再準備兩個以太坊賬戶,以便在創世時存入資產。 本文首發于深入淺出區塊鏈社區原文鏈接:以太坊創世區塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。 創世區塊作為第零個區塊,其他區塊直接或間接引用到...
摘要:下面來看看具體是怎么實現接口的可以看到,啟動了多個線程調用函數,當有線程挖到時,會通過傳入的通道傳出結果。可以看到在主要循環中,不斷遞增的值,調用函數計算上面公式中的左邊,而則是公式的右邊。 前言 挖礦(mine)是指礦工節點互相競爭生成新區塊以寫入整個區塊鏈獲得獎勵的過程.共識(consensus)是指區塊鏈各個節點對下一個區塊的內容形成一致的過程在以太坊中, miner包向外提供挖...
摘要:月日上午點,共享云路由器在香港正式開售,售價港幣,用戶可使用香港本地手機號碼在香港電視注冊后進行選購。目前云鉆已上線競拍和區塊貓等落地應用,已有用戶使用云鉆成功競拍獲得。6月22日上午10點,360共享云路由器在香港正式開售,售價699港幣,用戶可使用香港本地手機號碼在香港電視 HKTVmall 注冊后進行選購。消息一經公開,便吸引了眾多關注。作為360的首款區塊鏈硬件產品,360共享云路由...
摘要:本文首發于深入淺出區塊鏈社區原文鏈接以太坊客戶端命令用法參數詳解原文已更新,請讀者前往原文閱讀在以太坊智能合約開發中最常用的工具必備開發工具,一個多用途的命令行工具。如果你還不知道是什么,請先閱讀入門篇以太坊是什么。 本文首發于深入淺出區塊鏈社區原文鏈接:以太坊客戶端Geth命令用法-參數詳解原文已更新,請讀者前往原文閱讀 Geth在以太坊智能合約開發中最常用的工具(必備開發工具),一...
閱讀 3529·2021-11-22 11:59
閱讀 945·2021-09-27 13:36
閱讀 3603·2021-09-24 09:47
閱讀 2251·2021-09-01 11:39
閱讀 970·2021-08-31 09:37
閱讀 2304·2021-08-05 10:01
閱讀 1665·2019-08-30 15:55
閱讀 693·2019-08-30 15:54