以太坊做爲一種數字貨幣以太幣的運行系統,顯然它也會有相似於錢包的客戶端程序,用來提供管理帳戶餘額等功能。咱們知道,存放(或者綁定,掛靠)以太幣的帳戶,在代碼中以Address類型變量存在,因此可以管理多個以太坊帳戶應該屬於客戶端程序基本功能之一。本文會從管理帳戶信息的代碼包開始,自底向上的介紹以太坊客戶端程序的一些主要模塊。node
在以太坊源代碼的accounts代碼包中,呈現帳戶地址的最小結構體叫Account{},它的主要成員就是一個common.Address類型變量;管理Account的接口類叫Wallet,類如其名,<Wallet>聲明瞭諸如緩存Account對象及解析Account對象等操做,管理多個<Wallet>對象的結構體叫Manager,這些類型的UML關係以下圖所示:git
在accounts代碼包內部的各類結構體/接口中,accounts.Manager在相互調用關係上無疑是處於頂端的,它自己是公共類,向外暴露包括查詢單個Account,返回單個或多個Wallet對象,訂閱Wallet更新事件等方法。在其內部它維持一個Wallet列表,經過每一個Wallet實現類持有一組Account帳戶對象,並經過一個event.Feed成員變量來管理全部向它訂閱Wallet更新事件的需求。golang
着重介紹一下這裏的訂閱(subscribe)操做,Manager的Subscribe()函數定義以下:算法
首先注意這個Subscribe()函數是讓外部調用對象 向該Manager做訂閱的操做,事實上該Manager自己也是經過相同的訂閱機制去獲知新添的Wallet對象,它的成員變量updates就是該Manager自己獲得所訂閱事件的通道。其次,Manager.Subscribe()函數只有一個chan參數,因爲golang語言中channel機制的強大,訂閱操做僅僅須要一個chan對象就足夠了,真是簡單之極,根本沒必要知道背後是誰發起了訂閱。儘管如此,這裏依然值得思考的是,到底是什麼對象向Manager發起了訂閱呢?其實,向某個Manager對象訂閱Wallet更新事件的,正是另一個Manager對象,也就是<Backend>的實現類。spring
得出以上這個結論,是頗有意義的。後面能夠了解到,accounts.Manager主要做爲eth.Ethereum(或者les.Ethereum)的一個成員存在,而這個eth.Ethereum是以太坊客戶端程序中最主要的部分,它以服務的形式提供幾乎全部以太坊系統運行所需的功能,因此一個以太坊客戶端可視爲一個accounts.Manager的存在,那麼真相就是,全部以太坊客戶端之間在經過accouts.Manager相互訂閱Wallet更新事件。數據庫
除Manager以外,這裏其餘幾個重要的結構體還包括:緩存
<Wallet>是接口類型,它的實現體包括軟件錢包(keystore.keystoreWallet)和硬件錢包(usbwallet.wallet),注意這裏的硬件錢包是有實物的。<Wallet>之下的代碼體系對於外部都不是公共的,全部向外暴露的「錢包」對象以及相關更新事件,都是以<Wallet>形式存在。網絡
軟件實現Wallet主要經過本地存儲文件的方式來管理帳戶地址。同時,<Wallet>對象須要對交易或區塊對象提供數字簽名,這須要用到橢圓曲線數字簽名(ECDSA)中的公鑰+密鑰,而每一個公鑰也是某個帳戶地址(Address)的來源,因此咱們也須要本地存儲ECDSA的公鑰密鑰信息。以太坊中這個經過本地存儲文件的方案實現accounts.<Wallet>功能的機制被成爲keystore。數據結構
<Wallet>的軟件錢包實現的相關代碼都處於/accounts/keystore/路徑下,這組代碼的主要UML關係以下圖:app
keystoreWallet{}:它是accounts.<Wallet>的實現類,它有一個Account對象,用來表示自身的地址,並經過Account.URL()方法,來實現上層接口<Wallet>.URL()方法;另外有一個KeyStore{}對象,這是這組代碼中的核心類。
KeyStore{}:它爲keystoreWallet結構體提供全部與Account相關的實質性的數據和操做。KeyStore{}內部有兩個做數據緩存用的成員:
另外,KeyStore{}中有一個<keyStore>接口類型的成員storage,用來對存儲在本地文件中的公鑰信息Key作操做。
Unlocked{}:公鑰密鑰數據類Key{}的封裝類,其內部成員除了Key{}以外,還提供了一個chan類型變量abort,它會在KeyStore對於公鑰密鑰信息的管理機制中發揮做用。
Key{}:存放數字簽名公鑰密鑰的數據類,其內部顯式存儲了一個ecdsa.PrivateKey{}類型的成員變量,前文介紹過,Golang原生代碼包中的ecdsa.PrivateKey{}中含有PublicKey{}類型的成員。而Key{}中同時攜帶Address類型成員變量,也能夠避免公鑰向地址類型轉化的操做重複發生。
<keyStore>:這個接口類型聲明瞭操做Key的函數,注意它與KeyStore{}在名字上僅有一個字母大小寫的差別。
keyStorePassphrase{}:<keyStore>接口的實現類,它實現了以Web3 Secret Storage加密方法爲公鑰密鑰信息進行加密管理。
accountCache{}:在內存中緩存keystore中某個已知路徑下全部Account對象,可提供由Address類型查找到對應Account對象的操做。
fileCache{}:keystore中可觀察到的文件的緩存,它可對某個路徑下存放的文件進行掃描,分別返回新增文件,缺失文件,改動文件的集合。
watcher{}:用來監測某個路徑中存儲的帳戶文件的變化,能夠定時調用accountCache的方法對文件進行掃描。
accountCache緩存的賬號信息,均來自於某個已知路徑下存儲的本地文件集合。每一個文件都是JSON格式,以顯式存放Address: {Address: "@Address"},因此accountCache在讀取文件後,能夠直接轉化成Account{}對象,在代碼中使用。這裏以顯式文件存儲Address信息沒有任何問題,既不用擔憂Address信息泄露形成危害(沒法從Address反向解析出源頭的ECDSA所用公鑰),又能夠方便代碼調用。
在使用中,watcher對象會維護一個定時器,不斷的通知accountCache掃描某個給定的路徑;accountCache會調用fileCache對象去掃描該路徑下的文件,並根據fileCache返回的三種文件集合:新添文件、缺失文件、改動文件,在自身維護的Account集合中做相應操做。
Key{}經過ecdsa.PrivateKey對象從而同時攜帶ECDSA所用的公鑰密鑰,因此這裏涉及到公鑰密鑰部分,都是針對Key對象作的操做。keystore機制中,在本地存儲的是通過加密的Key對象的JSON格式,所用的加密方法被稱爲Web3 Secret Storage,其實現細節可在ethereum git wiki上找到。下圖是該存儲方式的簡單示意圖:
對一個加密存儲的Key對象作操做時,總共須要三個參數,包括調用方提供一個名爲passphrase的任意字符串,以及keyStorePassphrase{}中給定的兩個整型數scryptN,scryptP,這兩個整型參數在keyStorePassphrase對象生命週期內部是固定不變的,只能在建立時賦值。這樣不論是每次新存儲一個Key對象,仍是取出一個已存的Key對象,調用方都必須傳入正確的參數passphrase,因此在實際應用中,以太坊錢包的客戶必須自行記憶該字符串。實際上,客戶爲每一個帳戶建立的密碼password,程序中正是這個加密參數passphrase。
Key{}對象從加密過的本地文件中取出後,會被封裝成unlocked{}對象,並被KeyStore放進其map[Address]*unlocked類型成員中。因爲公鑰密鑰的重要性,顯然keystore中存有的unlocked對象也應該控制公開時長。對於不一樣的時限需求,KeyStore{}提供了以下兩個函數:
TimedUnlock()函數會在給定的時限到達後,當即將已知Account對應的unlocked對象中的PrivateKey的私鑰銷燬(逐個bit清0),並將該unlocked對象從KeyStore成員中刪除。而Unlock()函數會將該unlocked對象一直公開,直到程序退出。注意,這裏的清理工做僅僅是針對內存中的Key對象,而以加密方式存在本地的key文件不受影響。
keystore機制以本地文件的形式提供對帳戶信息和數字簽名公鑰私鑰的存儲和讀取,從而以軟件方式實現了accounts.<Wallet>的功能。它的兩套獨立的本地存儲文件,既考慮了公鑰私鑰的加密又兼顧了帳戶信息的快速讀取,體現出很全面的設計思路。
以太坊除了提供軟件實現的錢包以外,還有硬件實現的錢包。固然,對於硬件錢包,以太坊代碼中確定有上層代碼對此進行封裝。這些代碼都處於/accounts/usbwallet/下,它們的UML關係以下圖所示:
pkg accounts/usbwallet中 主要的結構包括wallet{}, Hub{}以及<driver>接口。
須要注意的是,在目前以太坊的主幹代碼中,硬件實現錢包有關數字簽名部分,目前只能提供針對交易進行原生的數字簽名功能,即僅僅<Wallet>.SignTx()函數可用,其餘簽名功能包括SignHash(),以及SignXXXWithPassphrase()均不支持,不知道其餘分支代碼是否有所不一樣。
在瞭解accounts代碼包以後,咱們就能夠來看看以太坊源代碼中最著名的類型,同時也是客戶端程序中最核心的部分 - eth.Ethereum。可以以整個系統名命名的結構體類型,想必功能應該很是強大,下圖是它的一個簡單UML圖:
上圖中央就是eth.Ethereum類型,四周都是它的成員變量類型,咱們來看看其中哪些是已經瞭解過的:
以上這些都是前文中都已經具體介紹過的代碼部分,接着再來看看那些新的類型:
特別介紹下LES:Light Ethereum Subprotocol(LES) 是爲輕量級客戶端專門設計的子協議。相比於eth.Ethereum提供全節點服務的客戶端,那些輕量級客戶端不參與挖掘新區塊,在與其餘節點的通訊中僅僅下載每一個區快的頭部(Block.Header),對於區塊鏈的其餘部分僅僅按需對部分同步。eth.Ehereum同時也支持LES,這樣一個提供全節點服務的客戶端就能夠與其餘輕量級客戶端以相同的協議通訊了。
對數字貨幣稍有了解的人應該都清楚p2p通訊協議對於此類「去中心化」系統的重大意義。的確,把p2p通訊協議稱爲以太坊系統的基石之一都不爲過,從代碼角度考慮, ProtocolManager及其代碼族 也屬於eth代碼包的一部分,不過因爲這部分代碼比較複雜,會在下一篇文章中專門介紹這些通訊協議的實現細節。
在瞭解eth.Ethereum這個核心服務以後,客戶端執行程序也就呼之欲出了。首先有一個node.Node{}做爲承載相似eth,Ethereum這樣服務模塊的容器:
Node{}對象內部有一個Service列表,全部實現了node.<Service>接口的對象均可以存放在Node裏,好比eth.Ethereum。
接着,go-ethereum的客戶端程序geth的代碼就很簡單了:
從命令行啓動geth客戶端的程序就是以上,建立一個node.Node對象,從配置中讀出想要註冊的服務名,而後一一建立相應的服務對象,Node去啓動它們。
geth是go-ethereum自帶的命令行客戶端程序,目前市場上也存在許多種其餘的以太坊客戶端程序,有興趣的讀者能夠去找來看看,有源代碼就最好了能夠比較一下。
以太坊的客戶端程序,本來應該是剛接觸以太坊的初學者最先遇到的部分之一。由於下載完整個源代碼包以後,按照相應語言的提示進行編譯,就會獲得一個客戶端的可執行程序。我最初首先看的客戶端的代碼,當追溯到eth.Ethereum{}結構體,看到那麼多模塊的成員變量時,就一會兒明白了,整個以太坊系統運行起來的基礎模塊是哪些部分。