[以太坊源代码分析] V. 从钱包到客户端
以太坊作為一種數(shù)字貨幣以太幣的運(yùn)行系統(tǒng),顯然它也會(huì)有類似于錢包的客戶端程序,用來提供管理賬戶余額等功能。我們知道,存放(或者綁定,掛靠)以太幣的賬戶,在代碼中以Address類型變量存在,所以能夠管理多個(gè)以太坊賬戶應(yīng)該屬于客戶端程序基本功能之一。本文會(huì)從管理賬戶信息的代碼包開始,自底向上的介紹以太坊客戶端程序的一些主要模塊。
1. 管理賬戶信息的代碼包accounts
在以太坊源代碼的accounts代碼包中,呈現(xiàn)賬戶地址的最小結(jié)構(gòu)體叫Account{},它的主要成員就是一個(gè)common.Address類型變量;管理Account的接口類叫Wallet,類如其名,<Wallet>聲明了諸如緩存Account對象及解析Account對象等操作,管理多個(gè)<Wallet>對象的結(jié)構(gòu)體叫Manager,這些類型的UML關(guān)系如下圖所示:
在accounts代碼包內(nèi)部的各種結(jié)構(gòu)體/接口中,accounts.Manager在相互調(diào)用關(guān)系上無疑是處于頂端的,它本身是公共類,向外暴露包括查詢單個(gè)Account,返回單個(gè)或多個(gè)Wallet對象,訂閱Wallet更新事件等方法。在其內(nèi)部它維持一個(gè)Wallet列表,通過每個(gè)Wallet實(shí)現(xiàn)類持有一組Account賬戶對象,并通過一個(gè)event.Feed成員變量來管理所有向它訂閱Wallet更新事件的需求。
Manager訂閱Wallet的更新事件
著重介紹一下這里的訂閱(subscribe)操作,Manager的Subscribe()函數(shù)定義如下:
[plain]?view plain?copy得出以上這個(gè)結(jié)論,是很有意義的。后面可以了解到,accounts.Manager主要作為eth.Ethereum(或者les.Ethereum)的一個(gè)成員存在,而這個(gè)eth.Ethereum是以太坊客戶端程序中最主要的部分,它以服務(wù)的形式提供幾乎所有以太坊系統(tǒng)運(yùn)行所需的功能,所以一個(gè)以太坊客戶端可視為一個(gè)accounts.Manager的存在,那么真相就是,所有以太坊客戶端之間在通過accouts.Manager相互訂閱Wallet更新事件。
除Manager之外,這里其他幾個(gè)重要的結(jié)構(gòu)體還包括:
- event.Feed{}:它可以管理一對多的訂閱模式,每個(gè)調(diào)用者提供一個(gè)chan對象,用以發(fā)送所訂閱的內(nèi)容。Feed{}處理的訂閱內(nèi)容是類型泛化的,而每一個(gè)Feed{}對象,在其生命周期內(nèi),只能處理一種類型的訂閱內(nèi)容,即向chan對象發(fā)送的value。Feed.Subscribe()方法返回<Subscription>接口的實(shí)現(xiàn)體feedSub{},Feed.Subscribe()幫助Manager實(shí)現(xiàn)了<Backend>所聲明的方法Subscribe()。在Feed結(jié)構(gòu)體內(nèi)部,CaseList被用來管理所有訂閱者發(fā)過來的chan對象。
- accounts.Account{}:它的成員除了一個(gè)common.Address類型,即20bytes長的地址變量外,還有一個(gè)可選成員URL,可以是網(wǎng)址,也可以是本地存儲(chǔ)的路徑+文件全名。在以網(wǎng)址形式存在時(shí),URL.Scheme就是網(wǎng)絡(luò)協(xié)議名,而作為本地存儲(chǔ)文件時(shí),URL.Scheme是字符串常量"keystore"。
- accounts.<Wallet>:它很像一般意義上的“錢包”,其管理的多個(gè)Account,恰似個(gè)人用戶在現(xiàn)實(shí)中擁有的多個(gè)銀行賬戶,每個(gè)Account上的Ether余額,可從數(shù)據(jù)庫(core.state.StateDB)中查詢。<Wallet>接口聲明的函數(shù)中,尤其需要注意的是SignXXX(),其中SignTx()是對一個(gè)Transaction(tx)對象進(jìn)行數(shù)字簽名,SignHash()是對一個(gè)Hash值進(jìn)行數(shù)字簽名,由于任何一個(gè)對象(只要可序列化)可以作Hash運(yùn)算,所以這里SignHash()其實(shí)是針對任何一個(gè)對象,尤其是Block區(qū)塊作數(shù)字簽名。
<Wallet>是接口類型,它的實(shí)現(xiàn)體包括軟件錢包(keystore.keystoreWallet)和硬件錢包(usbwallet.wallet),注意這里的硬件錢包是有實(shí)物的。<Wallet>之下的代碼體系對于外部都不是公共的,所有向外暴露的“錢包”對象以及相關(guān)更新事件,都是以<Wallet>形式存在。
軟件實(shí)現(xiàn)的Wallet - keystore
軟件實(shí)現(xiàn)Wallet主要通過本地存儲(chǔ)文件的方式來管理賬戶地址。同時(shí),<Wallet>對象需要對交易或區(qū)塊對象提供數(shù)字簽名,這需要用到橢圓曲線數(shù)字簽名(ECDSA)中的公鑰+密鑰,而每個(gè)公鑰也是某個(gè)賬戶地址(Address)的來源,所以我們也需要本地存儲(chǔ)ECDSA的公鑰密鑰信息。以太坊中這個(gè)通過本地存儲(chǔ)文件的方案實(shí)現(xiàn)accounts.<Wallet>功能的機(jī)制被成為keystore。
<Wallet>的軟件錢包實(shí)現(xiàn)的相關(guān)代碼都處于/accounts/keystore/路徑下,這組代碼的主要UML關(guān)系如下圖:
keystoreWallet{}:它是accounts.<Wallet>的實(shí)現(xiàn)類,它有一個(gè)Account對象,用來表示自身的地址,并通過Account.URL()方法,來實(shí)現(xiàn)上層接口<Wallet>.URL()方法;另外有一個(gè)KeyStore{}對象,這是這組代碼中的核心類。
KeyStore{}:它為keystoreWallet結(jié)構(gòu)體提供所有與Account相關(guān)的實(shí)質(zhì)性的數(shù)據(jù)和操作。KeyStore{}內(nèi)部有兩個(gè)作數(shù)據(jù)緩存用的成員:
- accountCache類型的成員cache,是所有待查找的地址信息(Account{}類型)集合;
- map[Address]unlocked{}形式的成員unlocked,由于unlocked{}結(jié)構(gòu)體僅僅簡單封裝了Key{}對象(Key{}中顯式含有數(shù)字簽名公鑰密鑰對),所以map[]中可通過Address變量查找到該地址對應(yīng)的原始公鑰以及密鑰。
另外,KeyStore{}中有一個(gè)<keyStore>接口類型的成員storage,用來對存儲(chǔ)在本地文件中的公鑰信息Key做操作。
Unlocked{}:公鑰密鑰數(shù)據(jù)類Key{}的封裝類,其內(nèi)部成員除了Key{}之外,還提供了一個(gè)chan類型變量abort,它會(huì)在KeyStore對于公鑰密鑰信息的管理機(jī)制中發(fā)揮作用。
Key{}:存放數(shù)字簽名公鑰密鑰的數(shù)據(jù)類,其內(nèi)部顯式存儲(chǔ)了一個(gè)ecdsa.PrivateKey{}類型的成員變量,前文介紹過,Golang原生代碼包中的ecdsa.PrivateKey{}中含有PublicKey{}類型的成員。而Key{}中同時(shí)攜帶Address類型成員變量,也可以避免公鑰向地址類型轉(zhuǎn)化的操作重復(fù)發(fā)生。
<keyStore>:這個(gè)接口類型聲明了操作Key的函數(shù),注意它與KeyStore{}在名字上僅有一個(gè)字母大小寫的差異。
keyStorePassphrase{}:<keyStore>接口的實(shí)現(xiàn)類,它實(shí)現(xiàn)了以Web3 Secret Storage加密方法為公鑰密鑰信息進(jìn)行加密管理。
accountCache{}:在內(nèi)存中緩存keystore中某個(gè)已知路徑下所有Account對象,可提供由Address類型查找到對應(yīng)Account對象的操作。
fileCache{}:keystore中可觀察到的文件的緩存,它可對某個(gè)路徑下存放的文件進(jìn)行掃描,分別返回新增文件,缺失文件,改動(dòng)文件的集合。
watcher{}:用來監(jiān)測某個(gè)路徑中存儲(chǔ)的賬戶文件的變化,可以定時(shí)調(diào)用accountCache的方法對文件進(jìn)行掃描。
本地文件顯式存儲(chǔ)賬戶信息
accountCache緩存的帳號信息,均來自于某個(gè)已知路徑下存儲(chǔ)的本地文件集合。每個(gè)文件都是JSON格式,以顯式存放Address: {Address: "@Address"},所以accountCache在讀取文件后,可以直接轉(zhuǎn)化成Account{}對象,在代碼中使用。這里以顯式文件存儲(chǔ)Address信息沒有任何問題,既不用擔(dān)心Address信息泄露造成危害(無法從Address反向解析出源頭的ECDSA所用公鑰),又可以方便代碼調(diào)用。
在使用中,watcher對象會(huì)維護(hù)一個(gè)定時(shí)器,不斷的通知accountCache掃描某個(gè)給定的路徑;accountCache會(huì)調(diào)用fileCache對象去掃描該路徑下的文件,并根據(jù)fileCache返回的三種文件集合:新添文件、缺失文件、改動(dòng)文件,在自身維護(hù)的Account集合中作相應(yīng)操作。
以本地加密文件存儲(chǔ)公鑰密鑰
Key{}通過ecdsa.PrivateKey對象從而同時(shí)攜帶ECDSA所用的公鑰密鑰,所以這里涉及到公鑰密鑰部分,都是針對Key對象做的操作。keystore機(jī)制中,在本地存儲(chǔ)的是經(jīng)過加密的Key對象的JSON格式,所用的加密方法被稱為Web3 Secret Storage,其實(shí)現(xiàn)細(xì)節(jié)可在ethereum git wiki上找到。下圖是該存儲(chǔ)方式的簡單示意圖:
對一個(gè)加密存儲(chǔ)的Key對象做操作時(shí),總共需要三個(gè)參數(shù),包括調(diào)用方提供一個(gè)名為passphrase的任意字符串,以及keyStorePassphrase{}中給定的兩個(gè)整型數(shù)scryptN,scryptP,這兩個(gè)整型參數(shù)在keyStorePassphrase對象生命周期內(nèi)部是固定不變的,只能在創(chuàng)建時(shí)賦值。這樣不管是每次新存儲(chǔ)一個(gè)Key對象,還是取出一個(gè)已存的Key對象,調(diào)用方都必須傳入正確的參數(shù)passphrase,所以在實(shí)際應(yīng)用中,以太坊錢包的客戶必須自行記憶該字符串。實(shí)際上,客戶為每個(gè)賬戶創(chuàng)建的密碼password,程序中正是這個(gè)加密參數(shù)passphrase。
取出的公鑰密鑰,在內(nèi)存中限時(shí)公開
Key{}對象從加密過的本地文件中取出后,會(huì)被封裝成unlocked{}對象,并被KeyStore放進(jìn)其map[Address]*unlocked類型成員中。由于公鑰密鑰的重要性,顯然keystore中存有的unlocked對象也應(yīng)該控制公開時(shí)長。對于不同的時(shí)限需求,KeyStore{}提供了如下兩個(gè)函數(shù):
[plain]?view plain?copykeystore機(jī)制以本地文件的形式提供對賬戶信息和數(shù)字簽名公鑰私鑰的存儲(chǔ)和讀取,從而以軟件方式實(shí)現(xiàn)了accounts.<Wallet>的功能。它的兩套獨(dú)立的本地存儲(chǔ)文件,既考慮了公鑰私鑰的加密又兼顧了賬戶信息的快速讀取,體現(xiàn)出很全面的設(shè)計(jì)思路。
硬件設(shè)備實(shí)現(xiàn)的Wallet
以太坊除了提供軟件實(shí)現(xiàn)的錢包之外,還有硬件實(shí)現(xiàn)的錢包。當(dāng)然,對于硬件錢包,以太坊代碼中肯定有上層代碼對此進(jìn)行封裝。這些代碼都處于/accounts/usbwallet/下,它們的UML關(guān)系如下圖所示:
pkg accounts/usbwallet中 主要的結(jié)構(gòu)包括wallet{}, Hub{}以及<driver>接口。
- wallet{}結(jié)構(gòu)體實(shí)現(xiàn)了上層接口accounts.<Wallet>,向外提供accounts.<Wallet>的函數(shù)實(shí)現(xiàn);
- <driver>?接口從命名就看得出來,它用來封裝下層硬件實(shí)現(xiàn)錢包的代碼。盡管嚴(yán)格來說,這個(gè)接口及其實(shí)現(xiàn)體跟一般意義上的"驅(qū)動(dòng)程序"沒什么關(guān)系。
- ledgerDriver{},trezorDriver{}?分別對應(yīng)于兩家供應(yīng)商發(fā)布的硬件數(shù)字貨幣錢包,Ledger 和 Trezor 分別是品牌名。它們都可以支持包括以太幣在內(nèi)的多種數(shù)字貨幣。
- <Hub>?結(jié)構(gòu)體,它實(shí)現(xiàn)了上層accounts.<Backend>接口,地位相當(dāng)于account.Manager。從代碼來看,所有硬件實(shí)現(xiàn)的<Wallet>部分,都會(huì)由這個(gè)Hub對象來管理。Hub{}向外以<Backend>接口的形式暴露,這樣更上層的代碼就不必區(qū)分下層錢包的具體實(shí)現(xiàn)是軟件還是硬件了。
需要注意的是,在目前以太坊的主干代碼中,硬件實(shí)現(xiàn)錢包有關(guān)數(shù)字簽名部分,目前只能提供針對交易進(jìn)行原生的數(shù)字簽名功能,即僅僅<Wallet>.SignTx()函數(shù)可用,其他簽名功能包括SignHash(),以及SignXXXWithPassphrase()均不支持,不知道其他分支代碼是否有所不同。
2. Ethereum服務(wù)
在了解accounts代碼包之后,我們就可以來看看以太坊源代碼中最著名的類型,同時(shí)也是客戶端程序中最核心的部分 -?eth.Ethereum。能夠以整個(gè)系統(tǒng)名命名的結(jié)構(gòu)體類型,想必功能應(yīng)該非常強(qiáng)大,下圖是它的一個(gè)簡單UML圖:
上圖中央就是eth.Ethereum類型,四周都是它的成員變量類型,我們來看看其中哪些是已經(jīng)了解過的:
- ethdb.<Database> 是對應(yīng)于core.state.StateDB{}的函數(shù)接口,有了<Database>接口類型的成員變量,可以在使用中調(diào)用StateDB{}
- consensus.<Engine> 是共識算法代碼包向外暴露的函數(shù)接口,其實(shí)現(xiàn)包括基于PoW的Ethash算法,和基于PoA的Clique算法。
- accounts.Manager 是管理賬戶信息和數(shù)字簽名公鑰密鑰信息的代碼。
- miner.Miner 是挖掘新區(qū)塊的代碼,它可以管理挖掘新區(qū)塊的整個(gè)流程,調(diào)用consensus.<Engine>完成新區(qū)塊的授勛/認(rèn)證,并向外廣播 新區(qū)塊事件。
- core.TxPool 是積累新交易(Transaction, tx)對象的代碼,每個(gè)新挖掘區(qū)塊,都需要從TxPool中監(jiān)聽Tx更新事件并獲取新交易集合以組裝成新區(qū)塊。
- core.BlockChain 是管理整個(gè)區(qū)塊鏈數(shù)據(jù)結(jié)構(gòu)的結(jié)構(gòu)體。
以上這些都是前文中都已經(jīng)具體介紹過的代碼部分,接著再來看看那些新的類型:
- node.<Service>,這是客戶端程序用以對節(jié)點(diǎn)進(jìn)行功能抽象的接口。每個(gè)客戶端都把自身視為網(wǎng)絡(luò)中的一個(gè)節(jié)點(diǎn)(node),這個(gè)節(jié)點(diǎn)向外所提供的所有功能,由<Service>接口來定義。
- <LesServer>:實(shí)現(xiàn)LES協(xié)議的函數(shù)接口,eth.<LesServer>其實(shí)是為了調(diào)用les.LesServer{}而專門創(chuàng)建的本地函數(shù)接口。
- EthApiBackend, 它是幫助Ethereum把各項(xiàng)功能以RPC 服務(wù)(service)的方式暴露出去的模塊,外部調(diào)用方以API的方式調(diào)用這些功能/服務(wù)。
- ProtocolManager,用來管理p2p通信。以太坊內(nèi)部把每個(gè)個(gè)體(peer)與其他個(gè)體群之間的通信協(xié)議稱為一種基于p2p通信協(xié)議的新協(xié)議??紤]到eth.Ethereum提供功能的全面性,它也被稱為全節(jié)點(diǎn)服務(wù)的通信協(xié)議。
- ProtocolManager的成員變量中,Fetcher用以接收其他個(gè)體發(fā)來的宣布挖掘出新區(qū)塊的消息并決定向?qū)Ψ将@取需要的部分,Downloader負(fù)責(zé)整個(gè)區(qū)塊鏈結(jié)構(gòu)的同步(下載)。
特別介紹下LES:Light Ethereum Subprotocol(LES)?是為輕量級客戶端專門設(shè)計(jì)的子協(xié)議。相比于eth.Ethereum提供全節(jié)點(diǎn)服務(wù)的客戶端,那些輕量級客戶端不參與挖掘新區(qū)塊,在與其他節(jié)點(diǎn)的通信中僅僅下載每個(gè)區(qū)快的頭部(Block.Header),對于區(qū)塊鏈的其他部分僅僅按需對部分同步。eth.Ehereum同時(shí)也支持LES,這樣一個(gè)提供全節(jié)點(diǎn)服務(wù)的客戶端就可以與其他輕量級客戶端以相同的協(xié)議通信了。
對數(shù)字貨幣稍有了解的人應(yīng)該都清楚p2p通信協(xié)議對于此類“去中心化”系統(tǒng)的重大意義。的確,把p2p通信協(xié)議稱為以太坊系統(tǒng)的基石之一都不為過,從代碼角度考慮, ProtocolManager及其代碼族 也屬于eth代碼包的一部分,不過由于這部分代碼比較復(fù)雜,會(huì)在下一篇文章中專門介紹這些通信協(xié)議的實(shí)現(xiàn)細(xì)節(jié)。
3.以太坊客戶端程序
在了解eth.Ethereum這個(gè)核心服務(wù)之后,客戶端執(zhí)行程序也就呼之欲出了。首先有一個(gè)node.Node{}作為承載類似eth,Ethereum這樣服務(wù)模塊的容器:
Node{}對象內(nèi)部有一個(gè)Service列表,所有實(shí)現(xiàn)了node.<Service>接口的對象都可以存放在Node里,比如eth.Ethereum。
接著,go-ethereum的客戶端程序geth的代碼就很簡單了:
[plain]?view plain?copygeth是go-ethereum自帶的命令行客戶端程序,目前市場上也存在許多種其他的以太坊客戶端程序,有興趣的讀者可以去找來看看,有源代碼就最好了可以比較一下。
小結(jié):
以太坊的客戶端程序,原本應(yīng)該是剛接觸以太坊的初學(xué)者最早遇到的部分之一。因?yàn)橄螺d完整個(gè)源代碼包之后,按照相應(yīng)語言的提示進(jìn)行編譯,就會(huì)得到一個(gè)客戶端的可執(zhí)行程序。我最初首先看的客戶端的代碼,當(dāng)追溯到eth.Ethereum{}結(jié)構(gòu)體,看到那么多模塊的成員變量時(shí),就一下子明白了,整個(gè)以太坊系統(tǒng)運(yùn)行起來的基礎(chǔ)模塊是哪些部分。
原文:http://blog.csdn.net/teaspring/article/details/78350888
與50位技術(shù)專家面對面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的[以太坊源代码分析] V. 从钱包到客户端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [以太坊源代码分析]III. 挖矿和共识
- 下一篇: [以太坊源代码分析] IV. 椭圆曲线密