以太坊做为一种数字货币以太币的运行系统,显然它也会有相似于钱包的客户端程序,用来提供管理帐户余额等功能。咱们知道,存放(或者绑定,挂靠)以太币的帐户,在代码中以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{}结构体,看到那么多模块的成员变量时,就一会儿明白了,整个以太坊系统运行起来的基础模块是哪些部分。