初鏈主網上線解讀之——抗ASIC
一. ASIC如何規避馮諾依曼瓶頸
在介紹初鏈的Truehash演算法之前,首先介紹一下什麼是ASIC。
回首這些年礦機的發展脈絡,會發現一條清晰的路徑:
CPU->GPU->FPGA->ASIC
那麼,ASIC為何戰勝了CPU/GPU,成為了主流礦機?其中很大一部分原因是CPU/GPU為了方便多樣性計算,採用了馮諾依曼架構:
馮諾依曼架構大致分三個部分,記憶體、控制單元、計算單元。運算程式與所運算的資料都寫在記憶體中,執行計算時候向計算單元傳輸。 傳輸頻寬所帶來的計算瓶頸,我們稱之為馮諾依曼瓶頸。
我們將積體電路可以簡單的看作多個場效電晶體的結合。CPU的能耗和有多少個場效應電晶體參與工作有關,還和頻率是正相關的。一個指令在CPU中的執行,要不要排程運算器,要不要訪問外存,要不要回寫,在不在L1中都會在調動電晶體數目上產生差別。
在挖礦效能上,少不了一個重要的指標: 每瓦Hash 。
在CPU中,一些不相干的指令,如:Fetch指令和decode佔據了大頭,而執行Hash的過程才佔據不到10%。這就是完成同樣功能,ASIC很省電,而CPU很費電的原因:ASIC不需要執行無關的指令,它將運算程式直接寫死在計算單元裡,只需要計算Hash即可。專心做一件事並做到最好,完美規避馮諾依曼瓶頸,這就是ASIC的優勢!
二. 抵制ASIC,意義何在
既然ASIC這麼強大,那麼使用ASIC不好嗎,為什麼要抵制它?
讓我們從數字貨幣的源頭去尋找,比特幣的白皮書,第6章,第一段:
每個區塊的第一筆交易進行特殊化處理,該交易產生一枚由該區塊創造者擁有的新的電子貨幣。這樣就增加了節點支援該網路的激勵,並在沒有中央集權機構發行貨幣的情況下,提供了一種將電子貨幣分配到流通領域的一種方法。這種將一定數量新貨幣持續增添到貨幣系統中的方法,非常類似於耗費資源去挖掘金礦並將黃金注入到流通領域。此時,CPU的時間和電力消耗就是消耗的資源。
這一章表述地非常明白,挖礦的本質就是 沒有中央集權背景下的印鈔和分發貨幣 。挖礦是一種公平的派發貨幣的過程。
有了以上基礎,我們開始進入——初鏈的Truehash演算法。
三. 初鏈Truehash演算法
從 BTC 之後,很多區塊鏈開發者一直在嘗試各種不同的抗 ASIC 挖礦演算法。從最終結果來看,這些嘗試無疑都是失敗的。在新一代公鏈中,初鏈研究團隊作為PoW的捍衛者和拯救者,獨創了本質上抗ASIC的挖礦演算法,可以實現演算法的隨機切換,使得再強大的ASIC也無法繞過馮諾依曼瓶頸,從而保護普通礦工的權益。
根本上抗 ASIC 的演算法,需要滿足以下幾點:
-
設 S 為一個演算法集合,所有實現了 S 的演算法,不能繞開馮諾依曼瓶頸。
-
每 T 個區塊切換一次演算法,切換方式必須滿足可驗證性和隨機性。
-
演算法切換不依賴手動調整。
在普通挖礦演算法中,將 blockheade, nonce等資訊經過 padding等運算之後,會形成一個向量 v(nonce)。
我們再來看一下初鏈的truehash演算法,它首先檢測dataset的狀態:
func (m *Minerva) CheckDataSetState(blockNum uint64) bool{ dataset := m.dataset if dataset.dateInit == 0{ //如果blockNum(區塊數)小於12000 if blockNum <= UPDATABLOCKLENGTH{ //初始化truehashTable m.truehashTableInit(dataset.evenDataset) dataset.dataset = &dataset.evenDataset }else{ //重點,在這裡開始進行資料集的置換 bn := (blockNum / UPDATABLOCKLENGTH -1 ) * UPDATABLOCKLENGTH + STARTUPDATENUM + 1 in :=(blockNum / UPDATABLOCKLENGTH) % 2 //如果 blockNum > UPDATABLOCKLENGTH 則改變 lookutable 生成奇數或偶數集 if in == 0{ //設定dataset的偶數資料集 dataset.dataset =&dataset.evenDataset //標誌位初始化 dataset.oddFlag = 0 dataset.evenFlag = 0 }else{ //設定dataset的奇數資料集 dataset.dataset =&dataset.oddDataset dataset.oddFlag = 0 dataset.evenFlag = 0 } //更新LookupTBL m.updateLookupTBL( bn, *dataset.dataset) } dataset.dateInit = 1 } if blockNum %UPDATABLOCKLENGTH >= STARTUPDATENUM { //開始更新 lookuptable in :=(blockNum / UPDATABLOCKLENGTH) % 2 //將 lookutable 進行奇偶切換 if in == 0{ //是偶數,將dadaset設定為奇數 if dataset.oddFlag == 0 { //更新LookupTBL res := m.updateLookupTBL(blockNum, dataset.oddDataset[:]) if res { //更新成功,將dataset奇數標誌位設為1 dataset.oddFlag = 1 }else{ //更新失敗 return false } } }else{ //是奇數,將dadaset設定為偶數 if dataset.evenFlag == 0 { res := m.updateLookupTBL(blockNum, dataset.evenDataset[:]) if res { dataset.evenFlag = 1 }else{ return false } } } } if blockNum %UPDATABLOCKLENGTH == 1{ in :=(blockNum / UPDATABLOCKLENGTH) % 2 //改變 lookutable,生成資料集 if in == 0{ //設定偶數資料集 dataset.dataset = &dataset.evenDataset dataset.evenFlag = 0 }else{ //設定奇數資料集 dataset.dataset = &dataset.oddDataset dataset.oddFlag = 0 } } return true }
這段函式涉及到的主要有truehashTable(truehash表),evenDataset(偶數集),oddDataSet(奇數集),實現的功能是:進行條件檢測,來生成區塊或置換dataset資料集。
這裡UPDATABLOCKLENGTH = 12000
如果區塊數小於等於12000個,那麼則初始化truehash表,生成區塊。
如果區塊數大於12000個,那麼開始進行dataset資料集的更換。
dataset資料集的改變,顯然影響到了truehash演算法的改變。接下來進入到truehash函式
// 計算這個nonce的工作量 digest, result := truehashFull(*m.dataset.dataset, hash, nonce)
看函式裡面的實現:
func fchainmining( plookup []uint64, header []byte, nonce uint64) ([]byte, []byte){ var seed [64]byte output := make([]byte, DGSTSIZE) val0 := uint32(nonce & 0xFFFFFFFF) //產生0~4294967295的一位數 val1 := uint32(nonce >> 32) fork:= 3; k >= 0; k-- { seed[k] = byte(val0) & 0xFF val0 >>= 8 } for k := 7; k >= 4; k-- { seed[k] = byte(val1) & 0xFF val1 >>= 8 } dgst := make([]byte, DGSTSIZE) for k := 0; k < HEADSIZE; k++{ seed[k+8] = header[k] } sha512 := makeHasher(sha3.New512()) var sha512_out [64]byte sha512(sha512_out[:],seed[:]) byteReverse(sha512_out[:]) var permute_in [32]uint64 for k := 0; k < 8; k++{ for x := 0; x < 8; x++{ var sft int= x * 8 val := (uint64(sha512_out[k*8+x]) << uint(sft)) permute_in[k] += val } } for k := 1; k < 4; k++{ for x := 0; x < 8; x++ { permute_in[k * 8 + x] = permute_in[x] } } scramble(permute_in[:], plookup[:]) var dat_in [256]byte for k := 0; k < 32; k++ { val := permute_in[k] for x := 0; x < 8; x++{ dat_in[k * 8 + x] = byte(val & 0xFF) val = val >> 8 } } for k := 0; k < 64; k++ { var temp byte temp = dat_in[k * 4]; dat_in[k * 4] = dat_in[k * 4 + 3]; dat_in[k * 4 + 3] = temp; temp = dat_in[k * 4 + 1]; dat_in[k * 4 + 1] = dat_in[k * 4 + 2]; dat_in[k * 4 + 2] = temp; } //用sha256生成隨機hash sha256 := makeHasher(sha3.New256()) sha256(output, dat_in[:]) // reverse byte for k := 0; k < DGSTSIZE; k++{ dgst[k] = output[k]; } returndgst, dgst }
裡面有眾多的移位操作、置換操作,其中用到了加密演算法sha256演算法,sha512演算法,然後通過makeHasher()函式生成隨機hash。
還有一個很關鍵的函式scramble(permute_in[:], plookup[:])
func scramble( permute_in []uint64, plookup []uint64) int{ var ptbl []uint64 var permute_out [32]uint64 for k := 0; k < 64; k++ { sf := int(permute_in[0] & 0x7f) bs := int(permute_in[31] >> 60) ptbl = plookup[bs * 2048 * 32:] MatMuliple(permute_in[:], permute_out[:], ptbl[:]) shift2048(permute_out[:], sf) for k := 0; k < 32; k++ { permute_in[k] = permute_out[k] permute_out[k] = 0 } } return 0 }
由此我們可以看到,通過改變dataset集,打亂輸入hash運算的資料來實現hash演算法的改變。這個資料集極為複雜,那麼這個演算法集合全部寫死在計算單元內的概率將幾乎為0。由於演算法會 隨機切換 ,馮諾依曼瓶頸將無法繞過。
truehash的演算法切換原理的總結:每 12000個 PoW區塊(生成大概需要83天)換一次dataset資料集。新的dataset資料集資訊由上個週期的第 1 – 8192個區塊所組成,組成方式通過分析第 11001 - 11256個區塊的雜湊值所產生。由於區塊的hash值不可提前預知,在第 11256個區塊出現之前,新演算法的資訊更是無從得知。從上週期的第 11257個區塊,到該演算法作廢,總共只有 88天的時間,這麼短的時間內生產 ASIC沒有意義,從而抵制礦機的形成慾望。
四.水果鏈整合PoW,終結大礦主時代
在目前的礦機生態下,PoW慢鏈需要避免Selfish Mining Attack和Eclipse Attack等問題。初鏈TrueChain團隊大膽嘗試用fPoW協議(FruitChain)替代中本聰的原始PoW協議(Nakamoto PoW),從工程上實現了將水果鏈整合到PoW中,從而成為fPOW。
fPOW是全新的挖礦設計理念,它採用了水果鏈(FruitChain)的形象設計。它的亮點如下:
-
低難度挖礦:水果的挖礦難度是正常區塊挖礦難度的1/600,每個水果記錄了若干交易信息,普通挖礦只需要驗證交易資訊即可,因而無需加入礦池,即可實現挖礦,避免了算力集中(形成礦池)。
-
抵禦私自挖礦:只要水果是新鮮並且存在的,那麼便可獲得獎勵。
-
挖水果獲得報酬:挖礦的礦工把挖到的水果接上Block後,會按照計算能力比例分配報酬。
水果鏈的實現,使得普通挖礦者可以更加公平地參與到挖礦過程中,並獲得更加公平地獎勵分配,這使初鏈成為名副其實的去中心化公鏈,終結了大礦主時代。
作者:weixin_39029194
原文:https://blog.csdn.net/weixin_39029194/article/details/83281012