Go學習之go-ethereum【以太坊】原始碼分析(一)
關於Go語言環境的安裝與配置,我在《入門篇》進行了詳細講解,有需要的朋友可以前往閱讀,本文進入當下比較火熱的區塊鏈專案 - 以太坊(go-ethereum)進行原始碼解讀。本文內容純屬個人見解,有錯誤理解或者不足之處還請見諒,歡迎一起交流學習。
- 環境準備
- 以太坊初識
- go-ethereum 原始碼目錄
- 黃皮書、白皮書
- 以太坊定義
- 原始碼中的rlp(部分)
1、環境準備:
- Git安裝
- Go安裝與配置(“GOROOT、GOPATH等等”)
- Go開發者工具安裝(本主使用的是Goland)
- Win10 64位環境系統(系統不限,適用即可)
//本主配置的GOPATH路徑為, Windows環境開發
C:\Go\workspace
//使用命令進行下載原始碼:
go get github.com/ethereum/go-ethereum
下載之後使用GoLand開發工具開啟原始碼。進行測試:
由於以太坊的原始碼較多,可能下載之後會有相關的依賴沒有全部下載,如果出現報錯資訊,根據報錯資訊找到相應的行號,同樣使用go get命令下載即可。
2、以太坊的初步認識
相信可能還有部分朋友對以太坊是第一次接觸,在這裡就簡單地介紹一下。什麼是以太坊(來自百度百科)
隨著“區塊鏈”這一名詞的狂歡式普及,國內眾多巨頭公司紛紛張開雙臂擁抱,把區塊鏈當做網際網路時代的偉大顛覆性創新,一窩蜂研究怎樣把區塊鏈變成自己搶佔商業先機的工具。從本質上是解決信任問題、降低信任成本的技術方案,實現去中心化,去信用中介。
當然,目前就數字貨幣的市場交易和國內的政策正處於探索階段的原因,在17年9月,也釋出了《關於防範代幣發行融資風險公告》,以此要求各類金融行業組織應當做好政策解讀,督促會員單位自覺抵制與代幣發行融資交易及“虛擬貨幣”相關的非法金融活動,遠離市場亂象,加強投資者教育,共同維護正常的金融秩序。
但同時,藏在數字貨幣背後的區塊鏈技術被看好,在政策上,逐漸在越來越多的國家當中得到支援。如需要更加深入的瞭解區塊鏈知識,我會在後續的發文中進行詳細介紹。在這不做展開(跑題了,orz)。
3、go-ethereum 目錄
accounts | 以太坊賬戶管理 |
bmt | 二進位制的默克爾樹 |
build | 編譯和構建的一些指令碼和配置 |
cmd | 命令列工具 |
common | 公共的工具類 |
compression | 實現以太坊資料執行時的一些編碼 |
consensus | 共識演算法 |
core | 核心資料結構和演算法 |
crypto | hash演算法、加密演算法 |
eth | ETH協議 |
ethclient | RPC客戶端 |
ethdb | eth資料庫 |
ethstats | 提供網路狀態的報告 |
event | 處理實時事件 |
les | 以太坊的輕量級協議子集 |
light | 以太坊輕量級客戶端提供按需檢索的功能 |
log | 日誌資訊 |
metrics | 提供磁碟計數器 |
miner | 以太坊的區塊建立和挖礦 |
mobile | 移動端 |
node | 以太坊的多種型別的節點 |
p2p | 以太坊p2p網路協議 |
rlp | 以太坊序列化處理 |
rpc | 遠端方法呼叫 |
swarm | 網路處理 |
tests | 測試 |
trie | 以太坊重要的資料結構 |
whisper | 提供了whisper節點的協議 |
其它 | console、contracts等 |
4、以太坊黃皮書介紹
有興趣閱讀以太坊黃皮書具體內容的可以參考《以太坊黃皮書連載》中文版、《崔廣斌的黃皮書》中文版、wiki的《白皮書》英文版去詳細瞭解。其中講解了複雜的數學問題和看起來很闊怕的公式(orz,反正我是給跪了)。希望你們看完之後能有一些更深入的瞭解。
以太坊黃皮書是關於以太坊技術的實現規範。
《白皮書》
《黃皮書》
簡介
隨著網際網路連線了世界上絕大多數地方,全球資訊共享 的成本越來越低。比特幣網路通過共識機制、自願遵守的社 會合約, 實現一個去中心化的價值轉移系統且可以在全球範 圍內自由使用, 這樣的技術改革展示了它的巨大力量。這樣 的系統可以說是加密安全、基於交易的狀態機的一種具體 應用。後續類似這樣的系統, 如域名幣(Namecoin), 從最 原先的貨幣應用發展到了其它應用, 雖然它只是其中很簡單 的一種應用。
以太坊是一個嘗試達到通用性的技術專案, 可以構建任 何基於交易的狀態機。而且以太坊致力於為開發者提供一 個緊湊的、整合的端到端系統, 這個系統提供了一種可信的訊息傳遞計算框架讓開發者以一種前所未有的正規化來構建軟體。
一些基本規則
1、對於大多數的函式來說,都用大寫字母來標識。
2、元組一般用大寫字母來標識。
3、標量或者固定大小的位元組陣列都用小寫字母標識。 比如 n 代表交易的nonce, 有一些可能有例外,比如δ代表一個給定指令需要的堆疊資料的多少。
4、變長的位元組陣列一般用加粗的小寫字母。 比如 o 代表一個message call的輸出資料。對於某些重要的也可能使用加粗的大寫字母。
5、以太坊的定義
以太坊在整體上可以看作一個基於交易的狀態機:起始於一個創世塊(Genesis)狀態,然後隨著交易的執行狀態 逐步改變一直到最終狀態, 這個最終狀態是以太坊世界的權 威版本。狀態中包含的資訊有: 賬戶餘額、名譽度、信譽度、 現實世界的附屬資料等; 簡而言之,能包含電腦可以描繪 的任何資訊。因此,交易是連線兩個狀態的有效橋樑;“有效”非常重要—因為無效的狀態改變遠超過有效的狀態改變。
簡單來說,就是通過讀取一系列的輸入,然後根據這些輸入,會轉換成一個新的狀態出來。
6、原始碼中的rlp
RLP是Recursive Length Prefix的簡寫。是以太坊中的序列化方法,以太坊的所有物件都會使用RLP方法序列化為位元組陣列。這裡我希望先從黃皮書來形式化上了解RLP方法, 然後通過程式碼來分析實際的實現。目錄
decode.go | 解碼器,把RLP資料解碼為go的資料結構 |
decode_tail_test.go | 解碼器測試程式碼 |
decode_test.go | 解碼器測試程式碼 |
doc.go | 文件程式碼 |
encode.go | 編碼器,把GO的資料結構序列化為位元組陣列 |
encode_test.go | 編碼器測試程式碼 |
encode_example_test.go | 編碼器測試程式碼 |
raw.go | 未解碼的RLP資料 |
raw_test.go | RLP測試程式碼 |
typecache.go | 型別快取, 型別快取記錄了型別->(編碼器|解碼器)的內容 |
rlp的組合類別
- 位元組陣列
- 資料結構(類List)
var (
typeCacheMutex sync.RWMutex //讀寫鎖,用來在多執行緒的時候保護typeCache這個Map
typeCache = make(map[typekey]*typeinfo) //核心資料結構,儲存了型別->編解碼器函式
)
type typeinfo struct { //儲存了編碼器和解碼器函式
decoder
writer
}
type typekey struct {
reflect.Type
// the key must include the struct tags because they
// might generate a different decoder.
tags
}
其中的typeCache便是核心,value中儲存著對應的編碼和解碼器
獲取編碼器和解碼器:
func cachedTypeInfo(typ reflect.Type, tags tags) (*typeinfo, error) {
typeCacheMutex.RLock() //加讀鎖來保護,
info := typeCache[typekey{typ, tags}]
typeCacheMutex.RUnlock()
if info != nil { //如果成功獲取到資訊,那麼就返回
return info, nil
}
// not in the cache, need to generate info for this type.
typeCacheMutex.Lock() //否則加寫鎖 呼叫cachedTypeInfo1函式建立並返回, 這裡需要注意的是在多執行緒環境下有可能多個執行緒同時呼叫到這個地方,所以當你進入cachedTypeInfo1方法的時候需要判斷一下是否已經被別的執行緒先建立成功了。
defer typeCacheMutex.Unlock()
return cachedTypeInfo1(typ, tags)
}
func cachedTypeInfo1(typ reflect.Type, tags tags) (*typeinfo, error) {
key := typekey{typ, tags}
info := typeCache[key]
if info != nil {
// 其他的執行緒可能已經建立成功了, 那麼我們直接獲取到資訊然後返回
return info, nil
}
// put a dummmy value into the cache before generating.
// if the generator tries to lookup itself, it will get
// the dummy value and won't call itself recursively.
//這個地方首先建立了一個值來填充這個型別的位置,避免遇到一些遞迴定義的資料型別形成死迴圈
typeCache[key] = new(typeinfo)
info, err := genTypeInfo(typ, tags)
if err != nil {
// remove the dummy value if the generator fails
delete(typeCache, key)
return nil, err
}
*typeCache[key] = *info
return typeCache[key], err
}
生成對應型別的編解碼器函式:
func genTypeInfo(typ reflect.Type, tags tags) (info *typeinfo, err error) {
info = new(typeinfo)
if info.decoder, err = makeDecoder(typ, tags); err != nil {
return nil, err
}
if info.writer, err = makeWriter(typ, tags); err != nil {
return nil, err
}
return info, nil
}
----------------------------------------------------------------------
未完待續,敬請期待......
另外,文章部分內容和圖片來自ZtesoftCS的github、春花秋夢的以太坊原理,在此鳴謝。
有任何建議或問題,歡迎加微信一起學習交流,歡迎從事IT,熱愛IT,喜歡深挖原始碼的行業大牛加入,一起探討。
個人微訊號:bboyHan