├── README.md ├── cmd-process-analysis ├── .DS_Store ├── Geth启动流程代码解析.md ├── README.md ├── cmd中Geth重要衔接包代码简析.md ├── img │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ └── README.md ├── p-2-p通信流程代码分析.md ├── 从命令行开始解析以太坊update账户的源码.md ├── 从命令行开始解析以太坊列出账户地址的源码.md ├── 从命令行开始解析以太坊引入密钥文件的过程.md ├── 从命令行开始解析以太坊新建以太坊账户过程.md ├── 从命令行开始解析同步区块的代码分析.md ├── 从命令行开始解析生成创世块源码.md ├── 以太坊RPC-HTTP通信源码简析.md └── 具体的RPC-HTTP接口请求实例.md ├── dicorfile-process-analysis ├── README.md ├── accounts │ └── README.md ├── bmt │ └── README.md ├── build │ └── README.md └── cmd │ ├── README.md │ └── geth │ ├── README.md │ └── accountcmd.go.md ├── theory ├── README.md ├── UTXO.md └── 比特币交易签名过程.md └── wallet ├── NodeJs实现HD分层钱包的账户体系和转账签名.md ├── README.md ├── linkeye-wallet ├── README.md ├── img │ ├── README.md │ └── linkeye-wallet.png ├── linkeye钱包项目架构分析.md ├── 创建账户体系代码解析.md ├── 口令登陆和口令修改的源码解析.md ├── 导入私钥源码解析.md ├── 导出私钥源码解析.md ├── 获取账户余额源码解析.md ├── 获取转账记录的源码解析.md ├── 资金密码修改的源码解析.md ├── 转账的源码解析.md └── 转账确认区块扫描代码解析.md ├── material ├── README.md └── lib │ ├── bip系列.md │ ├── ethereumjs-tx.md │ ├── keythereum.md │ ├── web3js.md │ └── 开发钱包相关的库简介.md └── mist └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # 以太坊相关资料 2 | 3 | 4 | 5 | ### 以太坊代码解析 6 | 7 | * [以太坊理论部分资料汇总](https://github.com/guoshijiang/go-ethereum-code-analysis/tree/master/theory) 8 | * [安命令行执行流程解析以太坊源码](https://github.com/guoshijiang/go-ethereum-code-analysis/tree/master/cmd-process-analysis) 9 | * [按代码目录结构解析以太坊代码](https://github.com/guoshijiang/go-ethereum-code-analysis/tree/master/dicorfile-process-analysis) 10 | * [以太坊钱包源码及相关资料汇总](https://github.com/guoshijiang/go-ethereum-code-analysis/tree/master/wallet) 11 | 12 | 13 | # 以下附一则广告 14 | 15 | 16 | ## 有项目,来问我 17 | 18 | 问我技术团队成立于2016年,是一支专门为有需求的人提供技术咨询和技术服务的团队。目前团队共有7名成员,分别为郭世江,苏镇,张迎春,吕方,徐世军,龙剑锋,吴哲。下面是团队中各成员的基本信息,目前团队完成的项目,团队的合作伙伴以及团队的联系方式。 19 | 20 | ### 团队简介 21 | #### 郭世江 22 | 团队创始人,团队技术负责人,团队对外商务负责人;2016年毕业于黑龙江大学制药工程专业,2016年下半年进入中信银行,参与中信银行三方存管项目,中信银行B股,银衍项目的开发,主导中信银行保证证系统参数移置项目开发,独立设计开发华夏银行呼叫中心监控项目的中间转发层;2017年主导超球球场平台项目(包含从硬件底层到软件上层的一个大型项目)架构设计和开发;后来加入神州数字,区块链资深研究员,参与linkeye公链,linkeye征信链研发,主导linkeye钱包架构设计和开发,主导linkeye区块链浏览器项目的架构设计和开发;主导biwork项目的设计开发,主导设计开发家政服务超市后台和家政服务超市APP;在GitHub上著有《区块链钱包技术指南》一书,GitHub上译著《轻松玩转Docker》,GitHub上开源包含助记词生成,数字货币的地址私钥生成,私钥管理,交易签名等内容一套nodeJs区块链钱包开发库。 23 | 24 | ##### 参与的开源项目 25 | 26 | ###### linkeye区块链体系项: 27 | https://github.com/linkeye 28 | 29 | ###### 个人github主页: 30 | https://github.com/guoshijiang 31 | 32 | ###### 个人博客 33 | https://blog.csdn.net/jiang_xinxing 34 | 35 | ###### 个人公众号: 36 | .: 37 | ![.: 38 | ](https://github.com/guoshijiang/docker-virtual-technology/blob/master/images/%E5%9B%BE%E7%89%871.png) 39 | 40 | .: 41 | ![.: 42 | ](https://github.com/guoshijiang/docker-virtual-technology/blob/master/images/%E5%9B%BE%E7%89%872.png) 43 | 44 | 45 | #### 苏镇 46 | 47 | 苏镇高级前端开发工程师、高级项目经理,毕业于黑龙江大学。工作三年经验,精通JavaScript语言,熟悉多种前端开发框架,拥有对vue、angular、ionic、cordova等主流框架开发使用的实战经验,基于angular与ionic开发混合app,一次开发完美适配ios、android等各种机型,对前端网站项目性能与用户体验要求极高,追求极致的项目体验。掌握node、java等中台开发,可以独立使用node搭建服务端进行全栈开发。曾就职于北京中钢云科技有限公司担任项目主管,主导开发中钢云、中钢云物流app,打造钢铁行业的交易运输的领军者。现就职于鼎阅集团前端开发工程师,负责原创部门的码字猫pc客户端产品研发,致力于打造离线实时保存、在线实时云同步的精品写作平台。主要负责产品前端开发、技术选型、项目管理,参与大数据等服务端开发。 48 | 49 | #### 吕方 50 | 51 | 高级Java开发工程,资深大数据开发工程师,曾在西安华为任职,后进入北京睿动体育,参与超级球场平台的设计与开发,现任汽车之家高级开发工程师,参与汽车之家风控项目的设计与开发。主要负责大数据的设计和开发,微服务层的设计和开发。 52 | 53 | #### 张迎春 54 | 55 | 高级C++/golang/python开发工程师,高级爬虫工程师,2015年毕业于中北大学,参与中信银行三方存管、中信银行B股、银衍项目的设计和开发,北京文安智能科技的高级C++开发,负责后台服务器的设计和开发,参与文安智能科技繁星高密度视频分析集群项目的开发,在团队中主要负责数据抓取服务的设计与开发,项目的测试方案设计与执行。 56 | 57 | #### 徐世军 58 | 59 | 高级项目经理,高级PHP开发工程师,主导迪安体检项目的设计与开发。团队搜米域名网站负责人,西南家政APP后台架构。精通PHP,ThinkPHP,YII,尤其擅长微信小程序开发。主要负责微信端的业务设计与开发。 60 | 61 | #### 龙剑锋 62 | 63 | 高级前端开发工程师,5年的前端开发经验,精通H5、C3、JS、Node,Vue、React和electron等前端技术 64 | 65 | #### 吴泽华 66 | 67 | UI设计师,视觉交互设计师,拥有多年的互联网公司工作经验,超级球场项目,医宝网项目,协和医考通,医学人文和多伦多科技总设计师。 68 | 69 | 70 | ### 团队已完成的项目 71 | 72 | #### 项目一:搜米域名网站 73 | 74 | 网站访问地址:www.soumi.com 75 | 76 | 项目介绍:搜米域名网站是为搜米网络(厦门)科技有限公司打造的一个域名信息平台,用户可以通过该网站查看自己喜欢的域名,留下自己的信息后,搜米公司的运营团队获取用户信息之后和用户联系。通过该平台,用户可以通过该平台检查域名是否被墙。当然,该平台也不只有这些功能,详情请进入网站查看。 77 | 78 | #### 项目二.家政服务超市 79 | 80 | app下载: 81 | IOS版—appstore中可以直接下载 82 | Android版本—百度手机助手,安卓手机市场 83 | 84 | 项目介绍:该项目是为贵州毕节家艺家政保洁服务有限公司打造的集家政,电商,招聘,培训为一体化的APP和网站平台,具体的详情可以下载app查看。 85 | 86 | 87 | #### 项目三:某体检中心导引系统(Qt桌面应用) 88 | 89 | 该项目属于体检中心内部项目,没有任何公开的下载地址,在杭州和台州等多地体检中心已经上线该项目,该导引系统较简单,主要告知去体检的人已完成体检项目、未完成体检项目和各体检项目现有的排队人数。 90 | 91 | #### 项目四:鑫汇云APP 92 | 93 | 下载地址:iOS—appstore 94 | 安卓手机:应用宝,安卓市场等 95 | 96 | 河北钢铁制造业生产销售一体化 以企业采购、销售、物流需求为中心,助力每家企业轻松实现互联网+,拥有企业自己的“交易+物流+大数据+管理平台”。打造一个全新的产业互联网平台,一种全新的工作方式。 97 | 98 | •采购:发布采购抢单,撮合竞价排名,报价动态查看,实时感知上游企业的价格行情 99 | 100 | •销售:精准复合报价,实时同步买家,掌握实时需求,动态同步下游企业的采购变化 101 | 102 | •运输:实时指派物流,动态竞价排名,车辆实时同步,企业货物运输的一键调度派送 103 | 104 | •关系:通过加好友、生意圈、认证企业,可实时在线语音、文字、图片与企业实时沟通; 105 | 106 | •财务:实时更新采购、销售、物流交易数据,系统分类统计历史交易、即时交易; 107 | 108 | •质检:实时上传质检报告,跟踪货物流向,实现产品质量朔源; 109 | 110 | •优化:通过交易数据、交易频率、交易周期,分析用户撮合习惯和产品使用反馈,优化交易结果 111 | 112 | •决策:让云计算依靠交易公式、数据模型,让交易决策变成一秒; 113 | 114 | •效率:让原有的工作流程互联网化,大幅降低企业运营成本,全面提升升作效率; 115 | 116 | •分享:随时随地发生的采购、销售、物流需求,随即导入电子名片,实时动态分向全世界 117 | 118 | •动态:实时掌控企业员工动态、每一笔新发生的采购、销售、物流交易事件、交易进度; 119 | 120 | •管理:实现对集团所有供应商、采购商的管理分配,以及实时变更重要岗位的人员替换。 121 | 122 | 123 | #### 项目五:智能护腿板上位机测试软件(Qt桌面应用) 124 | 125 | 智能护腿板上位机测试软件是团队为其他公司专门打造的护腿板测试桌面软件,桌面程序和软件之间使用的是串口通信机制,软件和智能终端之间的通信方式是TCP和UDP Socket通信。该软件提供了串口配置功能,录入ID功能,数据发送频率录入功能,通信数据对比功能和心率数据测试功能。ID有一套单独的标签算法,该算法由团队总工郭世江设计。 126 | 127 | #### 项目六:智能终端上位机测试软件(Qt桌面应用) 128 | 129 | 智能终端上位机软件是团队专为体育公司打造的桌面测试软件,主要用于测试智能终端上的硬件设备,如:摄像头,电池,路由器,UWB电路板等设备的功能设备是否正常 130 | 131 | #### 项目七:分布式文件系统 132 | 133 | 团队专门打造的分布式文件系统,主要用于存储小型文件,后台系统,不是可视化的产品 134 | 135 | #### 项目八:分布式计算系统 136 | 137 | 体育公司的分布式计算系统,对运动员的运动数据进行计算和入库。 加速度计算:个人平均速度,个人最快速度,团队平均速度,团队最快速度,团队内成员最快速度计算:个人平均加速度,个人最快加速度,团队平均加速度,团队最快加速度,团队内成员最快加速度; 运动轨迹计算:个人运动轨迹,团队运动轨迹;热力图计算:个人热力图,团队热力图;平滑计算:将个人和团队的运动轨迹做平滑处理,误差小于1米;(平滑算法由总工郭世江设计)后台系统,不是可视化的产品。 138 | 139 | #### 项目九:区块链钱包 140 | 141 | 团队为杭州一家公司打造的一款移动端区块链HD钱包,支持单链,多链,支持多重签名方式数字资产托管;接入有BTC,ETH,ERC20 token和omni-USDT等币种,各地货币对应的币行情转换。 142 | 143 | #### 项目十:巡宇鑫联盟小程序 144 | 145 | 为巡宇投资咨询公司打造的一款宣传式小程序项目 146 | 147 | 148 | #### 项目十一:企业测评项目 149 | 150 | 正在开发中 151 | 152 | 目前正在洽谈中的项目有:智语文,素质宝宝,盛联盟APP 153 | 154 | ### 团队合作伙伴 155 | 156 | * 毕节家艺家政保洁服务有限公司 157 | 158 | * 云南三道文化传播有限公司 159 | 160 | * 天津爱皓睿网络科技有限公司 161 | 162 | * 北京启融科技有限公司 163 | 164 | * 天津巡宇投资咨询服务有限公司 165 | 166 | * 天津睿民新装饰工程有限公司 167 | 168 | 169 | 170 | 171 | ### 团队联系方式 172 | 173 | * 电话:18210042149 174 | 175 | * 电话:13611267041 176 | 177 | * 微信号:guo2012372 178 | 179 | * QQ: 1294928442 180 | 181 | * 邮箱:20123762@s.hlju.edu.cn 182 | 183 | -------------------------------------------------------------------------------- /cmd-process-analysis/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/cmd-process-analysis/.DS_Store -------------------------------------------------------------------------------- /cmd-process-analysis/Geth启动流程代码解析.md: -------------------------------------------------------------------------------- 1 | 2 | # Geth启动流程代码解析(完整解析) 3 | 4 | init函数主要是做了一些初始化的工作,其中比较重要的有三个地方,app.Action=geth,app.Commands中consoleCommand。启动流程走的是consoleCommand命令语句: 5 | 6 | func init() { 7 | // Initialize the CLI app and start Geth 8 | app.Action = geth 9 | app.HideVersion = true // we have a command to print the version 10 | app.Copyright = "Copyright 2013-2017 The go-ethereum Authors" 11 | app.Commands = []cli.Command{ 12 | // See chaincmd.go: 13 | initCommand, 14 | importCommand, 15 | exportCommand, 16 | removedbCommand, 17 | dumpCommand, 18 | // See monitorcmd.go: 19 | monitorCommand, 20 | // See accountcmd.go: 21 | accountCommand, 22 | walletCommand, 23 | // See consolecmd.go: 24 | consoleCommand, 25 | attachCommand, 26 | javascriptCommand, 27 | // See misccmd.go: 28 | makedagCommand, 29 | versionCommand, 30 | bugCommand, 31 | licenseCommand, 32 | // See config.go 33 | dumpConfigCommand, 34 | } 35 | app.Flags = append(app.Flags, nodeFlags...) 36 | app.Flags = append(app.Flags, rpcFlags...) 37 | app.Flags = append(app.Flags, consoleFlags...) 38 | app.Flags = append(app.Flags, debug.Flags...) 39 | app.Flags = append(app.Flags, whisperFlags...) 40 | app.Before = func(ctx *cli.Context) error { 41 | runtime.GOMAXPROCS(runtime.NumCPU()) 42 | if err := debug.Setup(ctx); err != nil { 43 | return err 44 | } 45 | // Start system runtime metrics collection 46 | go metrics.CollectProcessMetrics(3 * time.Second) 47 | utils.SetupNetwork(ctx) 48 | return nil 49 | } 50 | app.After = func(ctx *cli.Context) error { 51 | debug.Exit() 52 | console.Stdin.Close() // Resets terminal mode. 53 | return nil 54 | } 55 | } 56 | 57 | main函数的实现很简单,仅仅调用了app.Run函数,如果调用异常,则退出。 58 | 59 | func main() { 60 | if err := app.Run(os.Args); err != nil { 61 | fmt.Fprintln(os.Stderr, err) 62 | os.Exit(1) 63 | } 64 | } 65 | 66 | 67 | 此处通过第三方包gopkg.in/urfave/cli.v1来衔接,app中的action的类型是「func(*Context) error」,此时将执行a(context)方法,那么此时调用那个Action呢,其实就是我们前面提到的App.init()初始化命令时的consoleCommand,接下来我们来看看cmd/geth/consolecmd中的consoleCommand: 68 | 69 | consoleCommand = cli.Command{ 70 | Action: utils.MigrateFlags(localConsole), 71 | Name: "console", 72 | Usage: "Start an interactive JavaScript environment", 73 | Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...), 74 | Category: "CONSOLE COMMANDS", 75 | Description: ` 76 | The Geth console is an interactive shell for the JavaScript runtime environment 77 | which exposes a node admin interface as well as the Ðapp JavaScript API. 78 | See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`, 79 | } 80 | 81 | 其Action中的localConsole函数如下: 82 | 83 | func localConsole(ctx *cli.Context) error { 84 | // Create and start the node based on the CLI flags 85 | node := makeFullNode(ctx) 86 | startNode(ctx, node) 87 | defer node.Stop() 88 | 89 | // Attach to the newly started node and start the JavaScript console 90 | client, err := node.Attach() 91 | if err != nil { 92 | utils.Fatalf("Failed to attach to the inproc geth: %v", err) 93 | } 94 | config := console.Config{ 95 | DataDir: utils.MakeDataDir(ctx), 96 | DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), 97 | Client: client, 98 | Preload: utils.MakeConsolePreloads(ctx), 99 | } 100 | 101 | console, err := console.New(config) 102 | if err != nil { 103 | utils.Fatalf("Failed to start the JavaScript console: %v", err) 104 | } 105 | defer console.Stop(false) 106 | 107 | // If only a short execution was requested, evaluate and return 108 | if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { 109 | console.Evaluate(script) 110 | return nil 111 | } 112 | // Otherwise print the welcome screen and enter interactive mode 113 | console.Welcome() 114 | console.Interactive() 115 | 116 | return nil 117 | } 118 | 119 | 该函数非常重要,主要完成以下几件事情 120 | 121 | * 首先会创建一个节点、同时启动该节点 122 | * 创建一个console的实例 123 | * 显示Welcome信息 124 | * 创建一个无限循环用于在控制台交互 125 | 126 | 首先我们来看该Node是如何创建的。makeFullNode函数的实现如下: 127 | 128 | func makeFullNode(ctx *cli.Context) *node.Node { 129 | stack, cfg := makeConfigNode(ctx) 130 | 131 | utils.RegisterEthService(stack, &cfg.Eth) 132 | 133 | // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode 134 | shhEnabled := enableWhisper(ctx) 135 | shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name) 136 | if shhEnabled || shhAutoEnabled { 137 | if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) { 138 | cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name)) 139 | } 140 | if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) { 141 | cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name) 142 | } 143 | utils.RegisterShhService(stack, &cfg.Shh) 144 | } 145 | 146 | // Add the Ethereum Stats daemon if requested. 147 | if cfg.Ethstats.URL != "" { 148 | utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) 149 | } 150 | 151 | // Add the release oracle service so it boots along with node. 152 | if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 153 | config := release.Config{ 154 | Oracle: relOracle, 155 | Major: uint32(params.VersionMajor), 156 | Minor: uint32(params.VersionMinor), 157 | Patch: uint32(params.VersionPatch), 158 | } 159 | commit, _ := hex.DecodeString(gitCommit) 160 | copy(config.Commit[:], commit) 161 | return release.NewReleaseService(ctx, config) 162 | }); err != nil { 163 | utils.Fatalf("Failed to register the Geth release oracle service: %v", err) 164 | } 165 | return stack 166 | } 167 | 168 | 该函数首先创建一个Node,然后注册一个Ethereum Service,我们继续分析Node是如何创建的,makeConfigNode的函数实现逻辑如下: 169 | 170 | func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { 171 | // Load defaults. 172 | cfg := gethConfig{ 173 | Eth: eth.DefaultConfig, 174 | Shh: whisper.DefaultConfig, 175 | Node: defaultNodeConfig(), 176 | } 177 | 178 | // Load config file. 179 | if file := ctx.GlobalString(configFileFlag.Name); file != "" { 180 | if err := loadConfig(file, &cfg); err != nil { 181 | utils.Fatalf("%v", err) 182 | } 183 | } 184 | 185 | // Apply flags. 186 | utils.SetNodeConfig(ctx, &cfg.Node) 187 | stack, err := node.New(&cfg.Node) 188 | if err != nil { 189 | utils.Fatalf("Failed to create the protocol stack: %v", err) 190 | } 191 | utils.SetEthConfig(ctx, stack, &cfg.Eth) 192 | if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { 193 | cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) 194 | } 195 | 196 | utils.SetShhConfig(ctx, stack, &cfg.Shh) 197 | 198 | return stack, cfg 199 | } 200 | 201 | 首先初始化gethConfig的变量,接下来调用node.New方法来创建一个Node,我们来看看New函数的实现: 202 | 203 | func New(conf *Config) (*Node, error) { 204 | // Copy config and resolve the datadir so future changes to the current 205 | // working directory don't affect the node. 206 | confCopy := *conf 207 | conf = &confCopy 208 | if conf.DataDir != "" { 209 | absdatadir, err := filepath.Abs(conf.DataDir) 210 | if err != nil { 211 | return nil, err 212 | } 213 | conf.DataDir = absdatadir 214 | } 215 | // Ensure that the instance name doesn't cause weird conflicts with 216 | // other files in the data directory. 217 | if strings.ContainsAny(conf.Name, `/\`) { 218 | return nil, errors.New(`Config.Name must not contain '/' or '\'`) 219 | } 220 | if conf.Name == datadirDefaultKeyStore { 221 | return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) 222 | } 223 | if strings.HasSuffix(conf.Name, ".ipc") { 224 | return nil, errors.New(`Config.Name cannot end in ".ipc"`) 225 | } 226 | // Ensure that the AccountManager method works before the node has started. 227 | // We rely on this in cmd/geth. 228 | am, ephemeralKeystore, err := makeAccountManager(conf) 229 | if err != nil { 230 | return nil, err 231 | } 232 | // Note: any interaction with Config that would create/touch files 233 | // in the data directory or instance directory is delayed until Start. 234 | return &Node{ 235 | accman: am, 236 | ephemeralKeystore: ephemeralKeystore, 237 | config: conf, 238 | serviceFuncs: []ServiceConstructor{}, 239 | ipcEndpoint: conf.IPCEndpoint(), 240 | httpEndpoint: conf.HTTPEndpoint(), 241 | wsEndpoint: conf.WSEndpoint(), 242 | eventmux: new(event.TypeMux), 243 | }, nil 244 | } 245 | 246 | New函数首先将保存数据的目录转换成绝对路径,然后对配置进行相关的验证,确认是否合法,接下来创建一个AccountManager实例,然后创建一个Node节点,总体来说其内部的逻辑比较的简单。由于AccountManager负责Account的相关管理操作且后续经常使用,接下来我们看看是AccountManager的具体逻辑实现。AccountManager位于/node/config.go文件中,其实现如下: 247 | 248 | func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { 249 | scryptN := keystore.StandardScryptN 250 | scryptP := keystore.StandardScryptP 251 | if conf.UseLightweightKDF { 252 | scryptN = keystore.LightScryptN 253 | scryptP = keystore.LightScryptP 254 | } 255 | 256 | var ( 257 | keydir string 258 | ephemeral string 259 | err error 260 | ) 261 | switch { 262 | case filepath.IsAbs(conf.KeyStoreDir): 263 | keydir = conf.KeyStoreDir 264 | case conf.DataDir != "": 265 | if conf.KeyStoreDir == "" { 266 | keydir = filepath.Join(conf.DataDir, datadirDefaultKeyStore) 267 | } else { 268 | keydir, err = filepath.Abs(conf.KeyStoreDir) 269 | } 270 | case conf.KeyStoreDir != "": 271 | keydir, err = filepath.Abs(conf.KeyStoreDir) 272 | default: 273 | // There is no datadir. 274 | keydir, err = ioutil.TempDir("", "go-ethereum-keystore") 275 | ephemeral = keydir 276 | } 277 | if err != nil { 278 | return nil, "", err 279 | } 280 | if err := os.MkdirAll(keydir, 0700); err != nil { 281 | return nil, "", err 282 | } 283 | // Assemble the account manager and supported backends 284 | backends := []accounts.Backend{ 285 | keystore.NewKeyStore(keydir, scryptN, scryptP), 286 | } 287 | if !conf.NoUSB { 288 | if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { 289 | log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) 290 | } else { 291 | backends = append(backends, ledgerhub) 292 | } 293 | } 294 | return accounts.NewManager(backends...), ephemeral, nil 295 | } 296 | 297 | 由于在上面,我们已经将保存数据的目录转换成了绝对路径(由于在命令行,我们已经指定了datadir且存在,如果不存在将创建一个默认的go-ethereum-keystore来保存相关信息),接下来我们来看keystore.NewKeyStore函数。该函数位于/accounts/keystore/keystore.go文件中,其实现如下: 298 | 299 | func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { 300 | keydir, _ = filepath.Abs(keydir) 301 | ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}} 302 | ks.init(keydir) 303 | return ks 304 | } 305 | 306 | 首先转换成绝对路径(前面已经转换,应该没有必要再次检测),接下来我们看看 307 | 308 | ks.init(keydir) 309 | 310 | 方法的实现,其实现如下: 311 | 312 | 313 | func (ks *KeyStore) init(keydir string) { 314 | // Lock the mutex since the account cache might call back with events 315 | ks.mu.Lock() 316 | defer ks.mu.Unlock() 317 | 318 | // Initialize the set of unlocked keys and the account cache 319 | ks.unlocked = make(map[common.Address]*unlocked) 320 | ks.cache, ks.changes = newAccountCache(keydir) 321 | 322 | // TODO: In order for this finalizer to work, there must be no references 323 | // to ks. addressCache doesn't keep a reference but unlocked keys do, 324 | // so the finalizer will not trigger until all timed unlocks have expired. 325 | runtime.SetFinalizer(ks, func(m *KeyStore) { 326 | m.cache.close() 327 | }) 328 | // Create the initial list of wallets from the cache 329 | accs := ks.cache.accounts() 330 | ks.wallets = make([]accounts.Wallet, len(accs)) 331 | for i := 0; i < len(accs); i++ { 332 | ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} 333 | } 334 | } 335 | 336 | 该方法首先从缓存中(如果缓存存在则直接获取,否则从datadir中读取)获取Account,然后保存在keystore的钱包中,我们主要来看看ks.cache.accounts()函数,其实现逻辑如下: 337 | 338 | func (ac *accountCache) accounts() []accounts.Account { 339 | ac.maybeReload() 340 | ac.mu.Lock() 341 | defer ac.mu.Unlock() 342 | cpy := make([]accounts.Account, len(ac.all)) 343 | copy(cpy, ac.all) 344 | return cpy 345 | } 346 | 347 | 如果账户数据未被加载到内存中,则首先加载进来,然后copy一份,防止外面对缓存的账户做修改,我们来看看ac.maybeReload是如何加载账户信息的。其实现如下: 348 | 349 | func (ac *accountCache) maybeReload() { 350 | ac.mu.Lock() 351 | defer ac.mu.Unlock() 352 | 353 | if ac.watcher.running { 354 | return // A watcher is running and will keep the cache up-to-date. 355 | } 356 | if ac.throttle == nil { 357 | ac.throttle = time.NewTimer(0) 358 | } else { 359 | select { 360 | case <-ac.throttle.C: 361 | default: 362 | return // The cache was reloaded recently. 363 | } 364 | } 365 | ac.watcher.start() 366 | ac.reload() 367 | ac.throttle.Reset(minReloadInterval) 368 | } 369 | 370 | 首先使用goroutine启动一个watcher来监测keystore目录,防止其变化。接下来调用reload方法加载账户信息,reload方法的实现如下: 371 | 372 | func (ac *accountCache) reload() { 373 | accounts, err := ac.scan() 374 | if err != nil { 375 | log.Debug("Failed to reload keystore contents", "err", err) 376 | } 377 | ac.all = accounts 378 | sort.Sort(ac.all) 379 | for k := range ac.byAddr { 380 | delete(ac.byAddr, k) 381 | } 382 | for _, a := range accounts { 383 | ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a) 384 | } 385 | select { 386 | case ac.notify <- struct{}{}: 387 | default: 388 | } 389 | log.Debug("Reloaded keystore contents", "accounts", len(ac.all)) 390 | } 391 | 392 | reload方法首先加载Account信息,然后通过channel的方式通知加载结束。Scan方法就是最终从文件中加载账户信息的地方,其实现如下: 393 | 394 | func (ac *accountCache) scan() ([]accounts.Account, error) { 395 | files, err := ioutil.ReadDir(ac.keydir) 396 | if err != nil { 397 | return nil, err 398 | } 399 | 400 | var ( 401 | buf = new(bufio.Reader) 402 | addrs []accounts.Account 403 | keyJSON struct { 404 | Address string `json:"address"` 405 | } 406 | ) 407 | for _, fi := range files { 408 | path := filepath.Join(ac.keydir, fi.Name()) 409 | if skipKeyFile(fi) { 410 | log.Trace("Ignoring file on account scan", "path", path) 411 | continue 412 | } 413 | logger := log.New("path", path) 414 | 415 | fd, err := os.Open(path) 416 | if err != nil { 417 | logger.Trace("Failed to open keystore file", "err", err) 418 | continue 419 | } 420 | buf.Reset(fd) 421 | // Parse the address. 422 | keyJSON.Address = "" 423 | err = json.NewDecoder(buf).Decode(&keyJSON) 424 | addr := common.HexToAddress(keyJSON.Address) 425 | switch { 426 | case err != nil: 427 | logger.Debug("Failed to decode keystore key", "err", err) 428 | case (addr == common.Address{}): 429 | logger.Debug("Failed to decode keystore key", "err", "missing or zero address") 430 | default: 431 | addrs = append(addrs, accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}) 432 | } 433 | fd.Close() 434 | } 435 | return addrs, err 436 | } 437 | 438 | 该方法首先获取该目录下的文件,然后读取文件内容,JSON解析其address字段。下面是我的datadir/keystore中文件的内容,格式化后如下: 439 | 440 | { 441 | "address":"a8f8687d0da839cef651cc0d2dc41bb2dc796293", 442 | "crypto":{ 443 | "cipher":"aes-128-ctr", 444 | "ciphertext":"142da6b140f5f4ddd48205997cfc6257312dde63e227579bdc a427a24661b9f8", 445 | "cipherparams":{ 446 | "iv":"8d45e1d8b9fbd72fa40799c3a2ca1d22" 447 | }, 448 | "kdf":"scrypt", 449 | "kdfparams":{ 450 | "dklen":32, 451 | "n":262144, 452 | "p":1, 453 | "r":8, 454 | "salt":"68d9776e818a5c1a92982752df1b3c66fc54a804d508a926519d33e046158959" 455 | }, 456 | "mac":"898b1f94fda7731215e521ea7ca7eb7d9afd9b8255c6dccd244c78af88123e7e" 457 | }, 458 | "id":"dfefe914-e328-4f7f-89a7-a85689b8e855", 459 | "version":3 460 | } 461 | 462 | 从json可以看出我这里只有一个账户的信息,因为以太坊对于每个用户都创建一个单独的文件来保存其信息。对于账户信息的加载,AccountManager的创建我们分析完了,同时整个Node也创建完成,此时我们来看以太坊是如何启动一个节点的。此时回到main函数,startNode函数的代码如下: 463 | 464 | func startNode(ctx *cli.Context, stack *node.Node) { 465 | // Start up the node itself 466 | utils.StartNode(stack) 467 | 468 | // Unlock any account specifically requested 469 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 470 | 471 | passwords := utils.MakePasswordList(ctx) 472 | unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") 473 | for i, account := range unlocks { 474 | if trimmed := strings.TrimSpace(account); trimmed != "" { 475 | unlockAccount(ctx, ks, trimmed, i, passwords) 476 | } 477 | } 478 | // Register wallet event handlers to open and auto-derive wallets 479 | events := make(chan accounts.WalletEvent, 16) 480 | stack.AccountManager().Subscribe(events) 481 | 482 | go func() { 483 | // Create an chain state reader for self-derivation 484 | rpcClient, err := stack.Attach() 485 | if err != nil { 486 | utils.Fatalf("Failed to attach to self: %v", err) 487 | } 488 | stateReader := ethclient.NewClient(rpcClient) 489 | 490 | // Open and self derive any wallets already attached 491 | for _, wallet := range stack.AccountManager().Wallets() { 492 | if err := wallet.Open(""); err != nil { 493 | log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) 494 | } else { 495 | wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader) 496 | } 497 | } 498 | // Listen for wallet event till termination 499 | for event := range events { 500 | if event.Arrive { 501 | if err := event.Wallet.Open(""); err != nil { 502 | log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) 503 | } else { 504 | log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", event.Wallet.Status()) 505 | event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader) 506 | } 507 | } else { 508 | log.Info("Old wallet dropped", "url", event.Wallet.URL()) 509 | event.Wallet.Close() 510 | } 511 | } 512 | }() 513 | // Start auxiliary services if enabled 514 | if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { 515 | // Mining only makes sense if a full Ethereum node is running 516 | var ethereum *eth.Ethereum 517 | if err := stack.Service(ðereum); err != nil { 518 | utils.Fatalf("ethereum service not running: %v", err) 519 | } 520 | // Use a reduced number of threads if requested 521 | if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { 522 | type threaded interface { 523 | SetThreads(threads int) 524 | } 525 | if th, ok := ethereum.Engine().(threaded); ok { 526 | th.SetThreads(threads) 527 | } 528 | } 529 | // Set the gas price to the limits from the CLI and start mining 530 | ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name)) 531 | if err := ethereum.StartMining(true); err != nil { 532 | utils.Fatalf("Failed to start mining: %v", err) 533 | } 534 | } 535 | } 536 | 537 | 该函数内部首先将节点启动起来,然后建立跟RPC Server建立一个连接,用于RPC通信。我们来看看节点如何启动起来的。/cmd/utils/cmd.go文件如下: 538 | 539 | func StartNode(stack *node.Node) { 540 | if err := stack.Start(); err != nil { 541 | Fatalf("Error starting protocol stack: %v", err) 542 | } 543 | go func() { 544 | sigc := make(chan os.Signal, 1) 545 | signal.Notify(sigc, os.Interrupt) 546 | defer signal.Stop(sigc) 547 | <-sigc 548 | log.Info("Got interrupt, shutting down...") 549 | go stack.Stop() 550 | for i := 10; i > 0; i-- { 551 | <-sigc 552 | if i > 1 { 553 | log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) 554 | } 555 | } 556 | debug.Exit() // ensure trace and CPU profile data is flushed. 557 | debug.LoudPanic("boom") 558 | }() 559 | } 560 | 561 | 该函数调用了Node.go中的start方法,其实现如下: 562 | 563 | func (n *Node) Start() error { 564 | n.lock.Lock() 565 | defer n.lock.Unlock() 566 | 567 | // Short circuit if the node's already running 568 | if n.server != nil { 569 | return ErrNodeRunning 570 | } 571 | if err := n.openDataDir(); err != nil { 572 | return err 573 | } 574 | 575 | // Initialize the p2p server. This creates the node key and 576 | // discovery databases. 577 | n.serverConfig = n.config.P2P 578 | n.serverConfig.PrivateKey = n.config.NodeKey() 579 | n.serverConfig.Name = n.config.NodeName() 580 | if n.serverConfig.StaticNodes == nil { 581 | n.serverConfig.StaticNodes = n.config.StaticNodes() 582 | } 583 | if n.serverConfig.TrustedNodes == nil { 584 | n.serverConfig.TrustedNodes = n.config.TrustedNodes() 585 | } 586 | if n.serverConfig.NodeDatabase == "" { 587 | n.serverConfig.NodeDatabase = n.config.NodeDB() 588 | } 589 | running := &p2p.Server{Config: n.serverConfig} 590 | log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) 591 | 592 | // Otherwise copy and specialize the P2P configuration 593 | services := make(map[reflect.Type]Service) 594 | for _, constructor := range n.serviceFuncs { 595 | // Create a new context for the particular service 596 | ctx := &ServiceContext{ 597 | config: n.config, 598 | services: make(map[reflect.Type]Service), 599 | EventMux: n.eventmux, 600 | AccountManager: n.accman, 601 | } 602 | for kind, s := range services { // copy needed for threaded access 603 | ctx.services[kind] = s 604 | } 605 | // Construct and save the service 606 | service, err := constructor(ctx) 607 | if err != nil { 608 | return err 609 | } 610 | kind := reflect.TypeOf(service) 611 | if _, exists := services[kind]; exists { 612 | return &DuplicateServiceError{Kind: kind} 613 | } 614 | services[kind] = service 615 | } 616 | // Gather the protocols and start the freshly assembled P2P server 617 | for _, service := range services { 618 | running.Protocols = append(running.Protocols, service.Protocols()...) 619 | } 620 | if err := running.Start(); err != nil { 621 | if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { 622 | return ErrDatadirUsed 623 | } 624 | return err 625 | } 626 | // Start each of the services 627 | started := []reflect.Type{} 628 | for kind, service := range services { 629 | // Start the next service, stopping all previous upon failure 630 | if err := service.Start(running); err != nil { 631 | for _, kind := range started { 632 | services[kind].Stop() 633 | } 634 | running.Stop() 635 | 636 | return err 637 | } 638 | // Mark the service started for potential cleanup 639 | started = append(started, kind) 640 | } 641 | // Lastly start the configured RPC interfaces 642 | if err := n.startRPC(services); err != nil { 643 | for _, service := range services { 644 | service.Stop() 645 | } 646 | running.Stop() 647 | return err 648 | } 649 | // Finish initializing the startup 650 | n.services = services 651 | n.server = running 652 | n.stop = make(chan struct{}) 653 | 654 | return nil 655 | } 656 | 657 | 该方法首先打开datadir目录,接着初始化serverConfig的相关配置,接着创建一个p2p.server的一个变量,然后启动该节点,在接着把RPC的Service启动起来,我们先看看节点的启动。/p2p/server.go中Start方法如下: 658 | 659 | func (srv *Server) Start() (err error) { 660 | srv.lock.Lock() 661 | defer srv.lock.Unlock() 662 | if srv.running { 663 | return errors.New("server already running") 664 | } 665 | srv.running = true 666 | log.Info("Starting P2P networking") 667 | 668 | // static fields 669 | if srv.PrivateKey == nil { 670 | return fmt.Errorf("Server.PrivateKey must be set to a non-nil key") 671 | } 672 | if srv.newTransport == nil { 673 | srv.newTransport = newRLPX 674 | } 675 | if srv.Dialer == nil { 676 | srv.Dialer = &net.Dialer{Timeout: defaultDialTimeout} 677 | } 678 | srv.quit = make(chan struct{}) 679 | srv.addpeer = make(chan *conn) 680 | srv.delpeer = make(chan peerDrop) 681 | srv.posthandshake = make(chan *conn) 682 | srv.addstatic = make(chan *discover.Node) 683 | srv.removestatic = make(chan *discover.Node) 684 | srv.peerOp = make(chan peerOpFunc) 685 | srv.peerOpDone = make(chan struct{}) 686 | 687 | // node table 688 | if !srv.NoDiscovery { 689 | ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase, srv.NetRestrict) 690 | if err != nil { 691 | return err 692 | } 693 | if err := ntab.SetFallbackNodes(srv.BootstrapNodes); err != nil { 694 | return err 695 | } 696 | srv.ntab = ntab 697 | } 698 | 699 | if srv.DiscoveryV5 { 700 | ntab, err := discv5.ListenUDP(srv.PrivateKey, srv.DiscoveryV5Addr, srv.NAT, "", srv.NetRestrict) //srv.NodeDatabase) 701 | if err != nil { 702 | return err 703 | } 704 | if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil { 705 | return err 706 | } 707 | srv.DiscV5 = ntab 708 | } 709 | 710 | dynPeers := (srv.MaxPeers + 1) / 2 711 | if srv.NoDiscovery { 712 | dynPeers = 0 713 | } 714 | dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict) 715 | 716 | // handshake 717 | srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)} 718 | for _, p := range srv.Protocols { 719 | srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) 720 | } 721 | // listen/dial 722 | if srv.ListenAddr != "" { 723 | if err := srv.startListening(); err != nil { 724 | return err 725 | } 726 | } 727 | if srv.NoDial && srv.ListenAddr == "" { 728 | log.Warn("P2P server will be useless, neither dialing nor listening") 729 | } 730 | 731 | srv.loopWG.Add(1) 732 | go srv.run(dialer) 733 | srv.running = true 734 | return nil 735 | } 736 | 737 | 该方法首先建立一个UDP连接,并且调用server.run启动起来,跟其它可用的节点建立连接等。关于如何建立连接,后续在讲P2P的时候,我们再来仔细的分析。到此,整个节点就启动起来了。接下来,我们来看前面4个步骤中的第二个–创建console实例。console.go中创建一个console的New方法实现如下: 738 | 739 | func New(config Config) (*Console, error) { 740 | // Handle unset config values gracefully 741 | if config.Prompter == nil { 742 | config.Prompter = Stdin 743 | } 744 | if config.Prompt == "" { 745 | config.Prompt = DefaultPrompt 746 | } 747 | if config.Printer == nil { 748 | config.Printer = colorable.NewColorableStdout() 749 | } 750 | // Initialize the console and return 751 | console := &Console{ 752 | client: config.Client, 753 | jsre: jsre.New(config.DocRoot, config.Printer), 754 | prompt: config.Prompt, 755 | prompter: config.Prompter, 756 | printer: config.Printer, 757 | histPath: filepath.Join(config.DataDir, HistoryFile), 758 | } 759 | if err := console.init(config.Preload); err != nil { 760 | return nil, err 761 | } 762 | return console, nil 763 | } 764 | 765 | 该函数首先对Config做一些默认设置,接着调用其jsre.New函数,在借这个调用init方法初始化相关RPC API。jsre.New方法的实现如下: 766 | 767 | func New(assetPath string, output io.Writer) *JSRE { 768 | re := &JSRE{ 769 | assetPath: assetPath, 770 | output: output, 771 | closed: make(chan struct{}), 772 | evalQueue: make(chan *evalReq), 773 | stopEventLoop: make(chan bool), 774 | } 775 | go re.runEventLoop() 776 | re.Set("loadScript", re.loadScript) 777 | re.Set("inspect", re.prettyPrintJS) 778 | return re 779 | } 780 | 781 | 该函数创建一个JSRE的变量然后使用goroutine的方式开启一个事件监听的循环,该事件为控制台输入后,经过一些处理通过channel的方式发送过来。具体接收到的命令后续处理,请读者自己跟踪其逻辑实现。总结下创建console的目的主要是监听控制台输入的命名。接下来我们来看看第三个步骤「显示Welcome信息 」,该信息在通过geth进入控制台的时候,会显示一些简单的信息,我们先来看看该函数的实现: 782 | 783 | func (c *Console) Welcome() { 784 | // Print some generic Geth metadata 785 | fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n") 786 | c.jsre.Run(` 787 | console.log("instance: " + web3.version.node); 788 | console.log("coinbase: " + eth.coinbase); 789 | console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); 790 | console.log(" datadir: " + admin.datadir); 791 | `) 792 | // List all the supported modules for the user to call 793 | if apis, err := c.client.SupportedModules(); err == nil { 794 | modules := make([]string, 0, len(apis)) 795 | for api, version := range apis { 796 | modules = append(modules, fmt.Sprintf("%s:%s", api, version)) 797 | } 798 | sort.Strings(modules) 799 | fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " ")) 800 | } 801 | fmt.Fprintln(c.printer) 802 | } 803 | 804 | 该方法首先打印「Welcome to the Geth JavaScript console!」信息,这个可以直接在控制台查看到,接着使用上面创建console的jsre执行四个控制台输出写版本、区块等信息。你可以直接复制出来在命令行执行,也能看到相同的结果。 805 | 806 | 接下来,我们来看最后的一个步骤「创建一个无限循环用于在控制台交互」,该函数的实现如下: 807 | 808 | func (c *Console) Interactive() { 809 | var ( 810 | prompt = c.prompt // Current prompt line (used for multi-line inputs) 811 | indents = 0 // Current number of input indents (used for multi-line inputs) 812 | input = "" // Current user input 813 | scheduler = make(chan string) // Channel to send the next prompt on and receive the input 814 | ) 815 | // Start a goroutine to listen for promt requests and send back inputs 816 | go func() { 817 | for { 818 | // Read the next user input 819 | line, err := c.prompter.PromptInput(<-scheduler) 820 | if err != nil { 821 | // In case of an error, either clear the prompt or fail 822 | if err == liner.ErrPromptAborted { // ctrl-C 823 | prompt, indents, input = c.prompt, 0, "" 824 | scheduler <- "" 825 | continue 826 | } 827 | close(scheduler) 828 | return 829 | } 830 | // User input retrieved, send for interpretation and loop 831 | scheduler <- line 832 | } 833 | }() 834 | // Monitor Ctrl-C too in case the input is empty and we need to bail 835 | abort := make(chan os.Signal, 1) 836 | signal.Notify(abort, os.Interrupt) 837 | 838 | // Start sending prompts to the user and reading back inputs 839 | for { 840 | // Send the next prompt, triggering an input read and process the result 841 | scheduler <- prompt 842 | select { 843 | case <-abort: 844 | // User forcefully quite the console 845 | fmt.Fprintln(c.printer, "caught interrupt, exiting") 846 | return 847 | 848 | case line, ok := <-scheduler: 849 | // User input was returned by the prompter, handle special cases 850 | if !ok || (indents <= 0 && exit.MatchString(line)) { 851 | return 852 | } 853 | if onlyWhitespace.MatchString(line) { 854 | continue 855 | } 856 | // Append the line to the input and check for multi-line interpretation 857 | input += line + "\n" 858 | 859 | indents = countIndents(input) 860 | if indents <= 0 { 861 | prompt = c.prompt 862 | } else { 863 | prompt = strings.Repeat(".", indents*3) + " " 864 | } 865 | // If all the needed lines are present, save the command and run 866 | if indents <= 0 { 867 | if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { 868 | if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { 869 | c.history = append(c.history, command) 870 | if c.prompter != nil { 871 | c.prompter.AppendHistory(command) 872 | } 873 | } 874 | } 875 | c.Evaluate(input) 876 | input = "" 877 | } 878 | } 879 | } 880 | } 881 | 882 | 883 | 884 | -------------------------------------------------------------------------------- /cmd-process-analysis/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 1.geth内部代码结构 3 | 4 | * abigen:一个源代码生成器,它将Ethereum智能合约定义(代码) 转换为易于使用的、编译时类型安全的Go package。 如果合约字节码也available的话,它可以在普通的Ethereum智能合约ABI上扩展功能。 同时也能编译Solidity源文件,使开发更加精简。 5 | 6 | * bootnode:该节点为Ethereum发现协议运行一个引导节点。 7 | 8 | * clef:Clef可以用来签署交易和数据,并且可以代替geth的账户管理。这使DApps不依赖于geth的账户管理。 当DApp想要签署数据时,它可以发送数据签名者,签名者将向用户提供上下文,并要求用户签署数据的许可。 如果用户授予签名者将签名发回给DApp的签名请求。此设置允许DApp连接到远程以太坊节点并发送本地签名的事务。 这个可以当DApp连接到远程节点时会有所帮助,因为本地Ethereum节点不可用,而不是与没有内置(或有限)帐户管理的链或特定以太坊节点同步。Clef可以在同一台机器上作为守护进程运行,或者关闭一个usb-stick,如[usbarmory](https://inversepath.com/usbarmory),或[QubesOS](https://www.qubes-os.org/)类型os设置中的单独虚拟机。 9 | 10 | * ethkey 11 | 12 | * evm:执行EVM代码片段 13 | 14 | * faucet:faucet是以太faucet支持的轻量级客户 15 | 16 | * geth:geth是Ethereum的官方客户端命令行,它是Ethereum网络(以太坊主网,测试网络或私有网)的入口点,使用此命令可以使节点作为full node(默认),或者archive node(保留所有历史状态)或light node(检索数据实时)运行。 其他进程可以通过暴露在HTTP,WebSocket和/或IPC传输之上的JSON RPC端点作为通向Ethereum网络的网关使用 17 | 18 | * internal 19 | 20 | * p2psim:p2psim为客户端命令行模拟HTTPAPI 21 | 22 | * puppeth:puppeth是一个命令组装和维护私人网路 23 | 24 | * rlpdump:rlpdump能更好的打印出RLP格式的数据 25 | 26 | * swarm:bzzhash命令能够更好的计算出swarm哈希树 27 | 28 | * utils: 为Go-Ethereum命令提供说明 29 | 30 | * wnode 31 | 32 | ### 2.Eth相关代码结构 33 | 34 | #### 一级目录 35 | 36 | * eth:以太坊协议 37 | 38 | * ethclient :以太坊RPC API客户端  39 | 40 | #### 2.二级目录 41 | 42 | * downloader:手动全链同步,主要负责区块链最开始的同步工作,当前的同步有两种模式, 一种是传统的fullmode,这种模式通过下载区块头,和区块体来构建区块链,同步的过程就和普通的区块插入的过程一样,包括区块头的验证,交易的验证,交易执行,账户状态的改变等操作,这其实是一个比较消耗CPU和磁盘的一个过程。另一种模式就是 快速同步的fast sync模式, 这种模式有专门的文档来描述。请参考fast sync的文档。简单的说 fast sync的模式会下载区块头,区块体和收据,插入的过程不会执行交易,然后在一个区块高度(最高的区块高度 - 1024)的时候同步所有的账户状态,后面的1024个区块会采用fullmode的方式来构建。 这种模式会加区块的插入时间,同时不会产生大量的历史的账户信息。会相对节约磁盘, 但是对于网络的消耗会更高。因为需要下载收据和状态。 43 | 44 | * fetcher:基于块通知的同步。接收到当我们接收到NewBlockHashesMsg消息得时候,我们只收到了很多Block的hash值。 需要通过hash值来同步区块 45 | 46 | * filters:用于区块,交易和日志事件的过滤,包包含了给用户提供过滤的功能,用户可以通过调用对交易或者区块进行过滤,然后持续的获取结果,如果5分钟没有操作,这个过滤器会被删除。 47 | 48 | * gasprice:提供gas的价格建议, 根据过去几个区块的gasprice,来得到当前的gasprice的建议价格 49 | 50 | * tracers:收集JavaScript交易追踪  51 | -------------------------------------------------------------------------------- /cmd-process-analysis/cmd中Geth重要衔接包代码简析.md: -------------------------------------------------------------------------------- 1 | 2 | Geth中的main函数中调用了一个Run方法,该方法属于包cli中的类App的一个成员方法,下面是Run函数的具体实现 3 | 4 | func (a *App) Run(arguments []string) (err error) { 5 | a.Setup() 6 | 7 | // handle the completion flag separately from the flagset since 8 | // completion could be attempted after a flag, but before its value was put 9 | // on the command line. this causes the flagset to interpret the completion 10 | // flag name as the value of the flag before it which is undesirable 11 | // note that we can only do this because the shell autocomplete function 12 | // always appends the completion flag at the end of the command 13 | shellComplete, arguments := checkShellCompleteFlag(a, arguments) 14 | 15 | // parse flags 16 | set, err := flagSet(a.Name, a.Flags) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | set.SetOutput(ioutil.Discard) 22 | err = set.Parse(arguments[1:]) 23 | nerr := normalizeFlags(a.Flags, set) 24 | context := NewContext(a, set, nil) 25 | if nerr != nil { 26 | fmt.Fprintln(a.Writer, nerr) 27 | ShowAppHelp(context) 28 | return nerr 29 | } 30 | context.shellComplete = shellComplete 31 | 32 | if checkCompletions(context) { 33 | return nil 34 | } 35 | 36 | if err != nil { 37 | if a.OnUsageError != nil { 38 | err := a.OnUsageError(context, err, false) 39 | HandleExitCoder(err) 40 | return err 41 | } 42 | fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) 43 | ShowAppHelp(context) 44 | return err 45 | } 46 | 47 | if !a.HideHelp && checkHelp(context) { 48 | ShowAppHelp(context) 49 | return nil 50 | } 51 | 52 | if !a.HideVersion && checkVersion(context) { 53 | ShowVersion(context) 54 | return nil 55 | } 56 | 57 | if a.After != nil { 58 | defer func() { 59 | if afterErr := a.After(context); afterErr != nil { 60 | if err != nil { 61 | err = NewMultiError(err, afterErr) 62 | } else { 63 | err = afterErr 64 | } 65 | } 66 | }() 67 | } 68 | 69 | if a.Before != nil { 70 | beforeErr := a.Before(context) 71 | if beforeErr != nil { 72 | fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) 73 | ShowAppHelp(context) 74 | HandleExitCoder(beforeErr) 75 | err = beforeErr 76 | return err 77 | } 78 | } 79 | 80 | args := context.Args() 81 | if args.Present() { 82 | name := args.First() 83 | c := a.Command(name) 84 | if c != nil { 85 | return c.Run(context) 86 | } 87 | } 88 | 89 | if a.Action == nil { 90 | a.Action = helpCommand.Action 91 | } 92 | 93 | // Run default Action 94 | err = HandleAction(a.Action, context) 95 | 96 | HandleExitCoder(err) 97 | return err 98 | } 99 | 100 | a.Setup仅仅是做了些简单的处理,比如相关的Auther、Email、重新创建Command切片等等.接下来我们看看下面的这个if判断 101 | 102 | if args.Present() { 103 | name := args.First() 104 | c := a.Command(name) 105 | if c != nil { 106 | return c.Run(context) 107 | } 108 | } 109 | 110 | 由于我们前面在控制台输入的命令(启动命令 geth + 参数)长度不为0,因此执行 111 | 112 | c.Run(context) 113 | 114 | 操作,此时的命令其实就是我们的console命令。接下来我们看看Run方法,Run方法的代码如下: 115 | 116 | func (c Command) Run(ctx *Context) (err error) { 117 | if len(c.Subcommands) > 0 { 118 | return c.startApp(ctx) 119 | } 120 | 121 | if !c.HideHelp && (HelpFlag != BoolFlag{}) { 122 | // append help to flags 123 | c.Flags = append( 124 | c.Flags, 125 | HelpFlag, 126 | ) 127 | } 128 | 129 | set, err := flagSet(c.Name, c.Flags) 130 | if err != nil { 131 | return err 132 | } 133 | set.SetOutput(ioutil.Discard) 134 | 135 | if c.SkipFlagParsing { 136 | err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) 137 | } else if !c.SkipArgReorder { 138 | firstFlagIndex := -1 139 | terminatorIndex := -1 140 | for index, arg := range ctx.Args() { 141 | if arg == "--" { 142 | terminatorIndex = index 143 | break 144 | } else if arg == "-" { 145 | // Do nothing. A dash alone is not really a flag. 146 | continue 147 | } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 148 | firstFlagIndex = index 149 | } 150 | } 151 | 152 | if firstFlagIndex > -1 { 153 | args := ctx.Args() 154 | regularArgs := make([]string, len(args[1:firstFlagIndex])) 155 | copy(regularArgs, args[1:firstFlagIndex]) 156 | 157 | var flagArgs []string 158 | if terminatorIndex > -1 { 159 | flagArgs = args[firstFlagIndex:terminatorIndex] 160 | regularArgs = append(regularArgs, args[terminatorIndex:]...) 161 | } else { 162 | flagArgs = args[firstFlagIndex:] 163 | } 164 | 165 | err = set.Parse(append(flagArgs, regularArgs...)) 166 | } else { 167 | err = set.Parse(ctx.Args().Tail()) 168 | } 169 | } else { 170 | err = set.Parse(ctx.Args().Tail()) 171 | } 172 | 173 | nerr := normalizeFlags(c.Flags, set) 174 | if nerr != nil { 175 | fmt.Fprintln(ctx.App.Writer, nerr) 176 | fmt.Fprintln(ctx.App.Writer) 177 | ShowCommandHelp(ctx, c.Name) 178 | return nerr 179 | } 180 | 181 | context := NewContext(ctx.App, set, ctx) 182 | if checkCommandCompletions(context, c.Name) { 183 | return nil 184 | } 185 | 186 | if err != nil { 187 | if c.OnUsageError != nil { 188 | err := c.OnUsageError(ctx, err, false) 189 | HandleExitCoder(err) 190 | return err 191 | } 192 | fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error()) 193 | fmt.Fprintln(ctx.App.Writer) 194 | ShowCommandHelp(ctx, c.Name) 195 | return err 196 | } 197 | 198 | if checkCommandHelp(context, c.Name) { 199 | return nil 200 | } 201 | 202 | if c.After != nil { 203 | defer func() { 204 | afterErr := c.After(context) 205 | if afterErr != nil { 206 | HandleExitCoder(err) 207 | if err != nil { 208 | err = NewMultiError(err, afterErr) 209 | } else { 210 | err = afterErr 211 | } 212 | } 213 | }() 214 | } 215 | 216 | if c.Before != nil { 217 | err = c.Before(context) 218 | if err != nil { 219 | fmt.Fprintln(ctx.App.Writer, err) 220 | fmt.Fprintln(ctx.App.Writer) 221 | ShowCommandHelp(ctx, c.Name) 222 | HandleExitCoder(err) 223 | return err 224 | } 225 | } 226 | 227 | if c.Action == nil { 228 | c.Action = helpSubcommand.Action 229 | } 230 | 231 | context.Command = c 232 | err = HandleAction(c.Action, context) 233 | 234 | if err != nil { 235 | HandleExitCoder(err) 236 | } 237 | return err 238 | } 239 | 240 | 该主要是设置flag、解析输入的命令行参数、创建全局的context、将当前命令保存到全局context中,接下来调用HandleAction来处理命令,HandleAction的函数实现如下: 241 | 242 | func HandleAction(action interface{}, context *Context) (err error) { 243 | if a, ok := action.(ActionFunc); ok { 244 | return a(context) 245 | } else if a, ok := action.(func(*Context) error); ok { 246 | return a(context) 247 | } else if a, ok := action.(func(*Context)); ok { // deprecated function signature 248 | a(context) 249 | return nil 250 | } else { 251 | return errInvalidActionType 252 | } 253 | } 254 | 255 | 通过该函数代码重新经过相应的Init函数中的命令初始化方式进入相应的命令执行,当然,这只是简单的分析,实际的执行过程比这锅复杂得多。 256 | -------------------------------------------------------------------------------- /cmd-process-analysis/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/cmd-process-analysis/img/1.png -------------------------------------------------------------------------------- /cmd-process-analysis/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/cmd-process-analysis/img/2.png -------------------------------------------------------------------------------- /cmd-process-analysis/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/cmd-process-analysis/img/3.png -------------------------------------------------------------------------------- /cmd-process-analysis/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/cmd-process-analysis/img/4.png -------------------------------------------------------------------------------- /cmd-process-analysis/img/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cmd-process-analysis/p-2-p通信流程代码分析.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析以太坊update账户的源码.md: -------------------------------------------------------------------------------- 1 | # 从命令行开始解析以太坊update账户的源码 2 | 3 | ![account](https://github.com/guoshijiang/go-ethereum-code-analysis/blob/master/cmd-process-analysis/img/3.png) 4 | 5 | main函数中的命令启动处代码,在main.go中的init函数中存在下面这个命令 6 | 7 | accountCommand 8 | 9 | 在账户更新的命令行代码,这里比较简单,没有什么需要解释的,命令的使用格式为:geth account update [options]
10 | 11 | { 12 | Name: "update", 13 | Usage: "Update an existing account", 14 | Action: utils.MigrateFlags(accountUpdate), 15 | ArgsUsage: "
", 16 | Flags: []cli.Flag{ 17 | utils.DataDirFlag, 18 | utils.KeyStoreDirFlag, 19 | utils.LightKDFFlag, 20 | }, 21 | Description: ` 22 | geth account update
23 | 24 | Update an existing account. 25 | 26 | The account is saved in the newest version in encrypted format, you are prompted 27 | for a passphrase to unlock the account and another to save the updated file. 28 | 29 | This same command can therefore be used to migrate an account of a deprecated 30 | format to the newest format or change the password for an account. 31 | 32 | For non-interactive use the passphrase can be specified with the --password flag: 33 | 34 | geth account update [options]
35 | 36 | Since only one password can be given, only format update can be performed, 37 | changing your password is only possible interactively. 38 | `, 39 | }, 40 | 41 | 42 | accountUpdate将帐户从以前的格式转换为当前帐户一个,也提供了改变口令的可能性,整个过程分为解锁账户,获取新的输入密码,更新账户成功 43 | 44 | 45 | func accountUpdate(ctx *cli.Context) error { 46 | if len(ctx.Args()) == 0 { 47 | utils.Fatalf("No accounts specified to update") 48 | } 49 | stack, _ := makeConfigNode(ctx) 50 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 51 | 52 | for _, addr := range ctx.Args() { 53 | account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) 54 | newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 55 | if err := ks.Update(account, oldPassword, newPassword); err != nil { 56 | utils.Fatalf("Could not update the account: %v", err) 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | 63 | 检索使用的帐户管理器 64 | 65 | func (n *Node) AccountManager() *accounts.Manager { 66 | return n.accman 67 | } 68 | 69 | 从帐户管理器中检索具有给定类型的后端 70 | 71 | func (am *Manager) Backends(kind reflect.Type) []Backend { 72 | return am.backends[kind] 73 | } 74 | 75 | 尝试解锁指定的账户三次,三次中如果有一次成功,那么就解锁成功,三次中一次都不能解锁账户成功,那么久解锁账户失败 76 | 77 | // tries unlocking the specified account a few times. 78 | func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 79 | //获取账户的地址 80 | account, err := utils.MakeAddress(ks, address) 81 | if err != nil { 82 | utils.Fatalf("Could not list accounts: %v", err) 83 | } 84 | 85 | //尝试解锁三次 86 | for trials := 0; trials < 3; trials++ { 87 | prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 88 | password := getPassPhrase(prompt, false, i, passwords) 89 | err = ks.Unlock(account, password) 90 | if err == nil { 91 | log.Info("Unlocked account", "address", account.Address.Hex()) 92 | return account, password 93 | } 94 | if err, ok := err.(*keystore.AmbiguousAddrError); ok { 95 | log.Info("Unlocked account", "address", account.Address.Hex()) 96 | return ambiguousAddrRecovery(ks, err, password), password 97 | } 98 | if err != keystore.ErrDecrypt { 99 | // No need to prompt again if the error is not decryption-related. 100 | break 101 | } 102 | } 103 | // All trials expended to unlock account, bail out 104 | // 尝试解锁账户失败 105 | utils.Fatalf("Failed to unlock account %s (%v)", address, err) 106 | 107 | return accounts.Account{}, "" 108 | } 109 | 110 | 111 | MakeAddress将直接指定的帐户转换为十六进制编码的字符串或密钥存储中的一个关键索引,用于内部帐户表示 112 | 113 | func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) { 114 | // If the specified account is a valid address, return it 115 | // 如果指定的账户是一个无效地址,直接返回 116 | if common.IsHexAddress(account) { 117 | return accounts.Account{Address: common.HexToAddress(account)}, nil 118 | } 119 | // Otherwise try to interpret the account as a keystore index 120 | // 否则,请尝试将该帐户解析为密钥库索引 121 | index, err := strconv.Atoi(account) 122 | if err != nil || index < 0 { 123 | return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) 124 | } 125 | log.Warn("-------------------------------------------------------------------") 126 | log.Warn("Referring to accounts by order in the keystore folder is dangerous!") 127 | log.Warn("This functionality is deprecated and will be removed in the future!") 128 | log.Warn("Please use explicit addresses! (can search via `geth account list`)") 129 | log.Warn("-------------------------------------------------------------------") 130 | 131 | accs := ks.Accounts() 132 | if len(accs) <= index { 133 | return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs)) 134 | } 135 | return accs[index], nil 136 | } 137 | 138 | 139 | 返回所有当前目录中的key文件 140 | 141 | func (ks *KeyStore) Accounts() []accounts.Account { 142 | return ks.cache.accounts() 143 | } 144 | 145 | func (ac *accountCache) accounts() []accounts.Account { 146 | ac.maybeReload() 147 | ac.mu.Lock() 148 | defer ac.mu.Unlock() 149 | cpy := make([]accounts.Account, len(ac.all)) 150 | copy(cpy, ac.all) 151 | return cpy 152 | } 153 | 154 | 155 | 检索与帐户关联的密码,或者从预先加载的密码短语列表中获取密码,或者从用户交互地请求密码 156 | 157 | func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { 158 | // If a list of passwords was supplied, retrieve from them 159 | if len(passwords) > 0 { 160 | if i < len(passwords) { 161 | return passwords[i] 162 | } 163 | return passwords[len(passwords)-1] 164 | } 165 | // Otherwise prompt the user for the password 166 | if prompt != "" { 167 | fmt.Println(prompt) 168 | } 169 | 170 | // 从控制台获取密码 171 | password, err := console.Stdin.PromptPassword("Passphrase: ") 172 | if err != nil { 173 | utils.Fatalf("Failed to read passphrase: %v", err) 174 | } 175 | 176 | // 从控制台获取确认密码 177 | if confirmation { 178 | confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 179 | if err != nil { 180 | utils.Fatalf("Failed to read passphrase confirmation: %v", err) 181 | } 182 | if password != confirm { 183 | utils.Fatalf("Passphrases do not match") 184 | } 185 | } 186 | return password 187 | } 188 | 189 | 根据密码解锁账户 190 | 191 | func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error { 192 | return ks.TimedUnlock(a, passphrase, 0) 193 | } 194 | 195 | TimedUnlock使用密码解锁给定帐户。 该帐户在超时期间保持解锁状态。 在程序退出之前,超时值为0会解锁该帐户。 该帐户必须匹配唯一的密钥文件 196 | 如果帐户地址已解锁一段时间,则TimedUnlock扩展或缩短活动解锁超时。如果地址先前无限期解锁,则超时不会更改 197 | 198 | func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error { 199 | a, key, err := ks.getDecryptedKey(a, passphrase) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | ks.mu.Lock() 205 | defer ks.mu.Unlock() 206 | u, found := ks.unlocked[a.Address] 207 | if found { 208 | if u.abort == nil { 209 | // The address was unlocked indefinitely, so unlocking 210 | // it with a timeout would be confusing. 211 | zeroKey(key.PrivateKey) 212 | return nil 213 | } 214 | // Terminate the expire goroutine and replace it below. 215 | close(u.abort) 216 | } 217 | if timeout > 0 { 218 | u = &unlocked{Key: key, abort: make(chan struct{})} 219 | go ks.expire(a.Address, u, timeout) 220 | } else { 221 | u = &unlocked{Key: key} 222 | } 223 | ks.unlocked[a.Address] = u 224 | return nil 225 | } 226 | 227 | AmbiguousAddrError是试图解锁存在多个文件的地址 228 | 229 | type AmbiguousAddrError struct { 230 | Addr common.Address 231 | Matches []accounts.Account 232 | } 233 | 234 | func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 235 | fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 236 | for _, a := range err.Matches { 237 | fmt.Println(" ", a.URL) 238 | } 239 | fmt.Println("Testing your passphrase against all of them...") 240 | var match *accounts.Account 241 | for _, a := range err.Matches { 242 | if err := ks.Unlock(a, auth); err == nil { 243 | match = &a 244 | break 245 | } 246 | } 247 | if match == nil { 248 | utils.Fatalf("None of the listed files could be unlocked.") 249 | } 250 | fmt.Printf("Your passphrase unlocked %s\n", match.URL) 251 | fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 252 | for _, a := range err.Matches { 253 | if a != *match { 254 | fmt.Println(" ", a.URL) 255 | } 256 | } 257 | return *match 258 | } 259 | 260 | 更新一个存在的账户 261 | 262 | func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error { 263 | a, key, err := ks.getDecryptedKey(a, passphrase) 264 | if err != nil { 265 | return err 266 | } 267 | // 将新的加密key存储 268 | return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) 269 | } 270 | 271 | 获取加密key 272 | 273 | func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) { 274 | a, err := ks.Find(a) 275 | if err != nil { 276 | return a, nil, err 277 | } 278 | // 获取key 279 | key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) 280 | return a, key, err 281 | } 282 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析以太坊列出账户地址的源码.md: -------------------------------------------------------------------------------- 1 | # 从命令行开始解析以太坊列出账户地址的源码 2 | 3 | ![account](https://github.com/guoshijiang/go-ethereum-code-analysis/blob/master/cmd-process-analysis/img/2.png) 4 | 5 | 6 | 命令行起始代码:函数init中的accountCommand 7 | 8 | func init() { 9 | // Initialize the CLI app and start Geth 10 | app.Action = geth 11 | app.HideVersion = true // we have a command to print the version 12 | app.Copyright = "Copyright 2013-2018 The go-ethereum Authors" 13 | app.Commands = []cli.Command{ 14 | // See chaincmd.go: 15 | initCommand, 16 | importCommand, 17 | exportCommand, 18 | importPreimagesCommand, 19 | exportPreimagesCommand, 20 | copydbCommand, 21 | removedbCommand, 22 | dumpCommand, 23 | // See monitorcmd.go: 24 | monitorCommand, 25 | // See accountcmd.go: 26 | accountCommand, 27 | walletCommand, 28 | // See consolecmd.go: 29 | consoleCommand, 30 | attachCommand, 31 | javascriptCommand, 32 | // See misccmd.go: 33 | makecacheCommand, 34 | makedagCommand, 35 | versionCommand, 36 | bugCommand, 37 | licenseCommand, 38 | // See config.go 39 | dumpConfigCommand, 40 | } 41 | sort.Sort(cli.CommandsByName(app.Commands)) 42 | 43 | app.Flags = append(app.Flags, nodeFlags...) 44 | app.Flags = append(app.Flags, rpcFlags...) 45 | app.Flags = append(app.Flags, consoleFlags...) 46 | app.Flags = append(app.Flags, debug.Flags...) 47 | app.Flags = append(app.Flags, whisperFlags...) 48 | 49 | app.Before = func(ctx *cli.Context) error { 50 | runtime.GOMAXPROCS(runtime.NumCPU()) 51 | if err := debug.Setup(ctx); err != nil { 52 | return err 53 | } 54 | // Start system runtime metrics collection 55 | go metrics.CollectProcessMetrics(3 * time.Second) 56 | 57 | utils.SetupNetwork(ctx) 58 | return nil 59 | } 60 | 61 | app.After = func(ctx *cli.Context) error { 62 | debug.Exit() 63 | console.Stdin.Close() // Resets terminal mode. 64 | return nil 65 | } 66 | } 67 | 68 | Account父命令下的子命令,执行`geth account list`这个命令,将会去执行列出账户的函数accountList 69 | 70 | Subcommands: []cli.Command{ 71 | { 72 | Name: "list", 73 | Usage: "Print summary of existing accounts", 74 | Action: utils.MigrateFlags(accountList), 75 | Flags: []cli.Flag{ 76 | utils.DataDirFlag, 77 | utils.KeyStoreDirFlag, 78 | }, 79 | Description: ` 80 | Print a short summary of all accounts`, 81 | }, 82 |                 83 | 获取账户列表并打印到控制台上 84 | 85 | func accountList(ctx *cli.Context) error { 86 | stack, _ := makeConfigNode(ctx) 87 | var index int 88 | for _, wallet := range stack.AccountManager().Wallets() { 89 | for _, account := range wallet.Accounts() { 90 | fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 91 | index++ 92 | } 93 | } 94 | return nil 95 | } 96 |   97 | 检索使用的帐户管理器 98 | 99 | func (n *Node) AccountManager() *accounts.Manager { 100 | return n.accman 101 | } 102 |     103 | 获取所有在这个账号管理下的所有签名的账户 104 | 105 | func (am *Manager) Wallets() []Wallet { 106 | am.lock.RLock() 107 | defer am.lock.RUnlock() 108 | 109 | cpy := make([]Wallet, len(am.wallets)) 110 | copy(cpy, am.wallets) 111 | return cpy 112 | } 113 |     114 | 账户数据结构 115 | 116 | type Account struct { 117 | Address common.Address `json:"address"` // Ethereum account address derived from the key 118 | URL URL `json:"url"` // Optional resource locator within a backend 119 | } 120 | 121 | Manager数据结构 122 | 123 | type Manager struct { 124 | backends map[reflect.Type][]Backend // Index of backends currently registered 125 | updaters []event.Subscription // Wallet update subscriptions for all backends 126 | updates chan WalletEvent // Subscription sink for backend wallet changes 127 | wallets []Wallet // Cache of all wallets from all registered backends 128 | 129 | feed event.Feed // Wallet feed notifying of arrivals/departures 130 | 131 | quit chan chan error 132 | lock sync.RWMutex 133 | } 134 |   135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析以太坊引入密钥文件的过程.md: -------------------------------------------------------------------------------- 1 | # 从命令行开始解析以太坊引入密钥文件的过程 2 | 3 | geth account import 文件名的命令起始 4 | 5 | { 6 | Name: "import", 7 | Usage: "Import a private key into a new account", 8 | Action: utils.MigrateFlags(accountImport), 9 | Flags: []cli.Flag{ 10 | utils.DataDirFlag, 11 | utils.KeyStoreDirFlag, 12 | utils.PasswordFileFlag, 13 | utils.LightKDFFlag, 14 | }, 15 | ArgsUsage: "", 16 | Description: ` 17 | geth account import 18 | 19 | Imports an unencrypted private key from and creates a new account. 20 | Prints the address. 21 | 22 | The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 23 | 24 | The account is saved in encrypted format, you are prompted for a passphrase. 25 | 26 | You must remember this passphrase to unlock your account in the future. 27 | 28 | For non-interactive use the passphrase can be specified with the -password flag: 29 | 30 | geth account import [options] 31 | 32 | Note: 33 | As you can directly copy your encrypted accounts to another ethereum instance, 34 | this import mechanism is not needed when you transfer an account between 35 | nodes. 36 | `, 37 | }, 38 | }, 39 |             40 | 下面是整个导入密钥文件的代码 41 | 42 | func accountImport(ctx *cli.Context) error { 43 | keyfile := ctx.Args().First() 44 | if len(keyfile) == 0 { 45 | utils.Fatalf("keyfile must be given as argument") 46 | } 47 | key, err := crypto.LoadECDSA(keyfile) 48 | if err != nil { 49 | utils.Fatalf("Failed to load the private key: %v", err) 50 | } 51 | stack, _ := makeConfigNode(ctx) 52 | passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 53 | 54 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 55 | acct, err := ks.ImportECDSA(key, passphrase) 56 | if err != nil { 57 | utils.Fatalf("Could not create the account: %v", err) 58 | } 59 | fmt.Printf("Address: {%x}\n", acct.Address) 60 | return nil 61 | } 62 | 63 | 从给定的文件中导入一个secp256k1的私钥 64 | 65 | func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { 66 | buf := make([]byte, 64) 67 | fd, err := os.Open(file) 68 | if err != nil { 69 | return nil, err 70 | } 71 | defer fd.Close() 72 | if _, err := io.ReadFull(fd, buf); err != nil { 73 | return nil, err 74 | } 75 | 76 | key, err := hex.DecodeString(string(buf)) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return ToECDSA(key) 81 | } 82 | 83 | ImportECDSA将给定密钥存储在密钥目录中,并使用密码对其进行加密 84 | 85 | func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { 86 | key := newKeyFromECDSA(priv) 87 | if ks.cache.hasAddress(key.Address) { 88 | return accounts.Account{}, fmt.Errorf("account already exists") 89 | } 90 | return ks.importKey(key, passphrase) 91 | } 92 | 93 | 94 | 关于newKeyFromECDSA函数这里不再说明,请参照《从命令行开始解析以太坊新建账户过程》文档 95 | 96 | 密钥导入 97 | 98 | func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) { 99 | a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}} 100 | if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { 101 | return accounts.Account{}, err 102 | } 103 | ks.cache.add(a) 104 | ks.refreshWallets() 105 | return a, nil 106 | } 107 | 108 | 在缓冲区加新账户 109 | 110 | func (ac *accountCache) add(newAccount accounts.Account) { 111 | ac.mu.Lock() 112 | defer ac.mu.Unlock() 113 | 114 | i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 115 | if i < len(ac.all) && ac.all[i] == newAccount { 116 | return 117 | } 118 | // newAccount is not in the cache. 119 | ac.all = append(ac.all, accounts.Account{}) 120 | copy(ac.all[i+1:], ac.all[i:]) 121 | ac.all[i] = newAccount 122 | ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 123 | } 124 | 125 | refreshWallets检索当前帐户列表,并根据该帐户进行必要的钱包刷新 126 | 127 | func (ks *KeyStore) refreshWallets() { 128 | // Retrieve the current list of accounts 129 | ks.mu.Lock() 130 | accs := ks.cache.accounts() 131 | 132 | // Transform the current list of wallets into the new one 133 | wallets := make([]accounts.Wallet, 0, len(accs)) 134 | events := []accounts.WalletEvent{} 135 | 136 | for _, account := range accs { 137 | // Drop wallets while they were in front of the next account 138 | for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 { 139 | events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped}) 140 | ks.wallets = ks.wallets[1:] 141 | } 142 | // If there are no more wallets or the account is before the next, wrap new wallet 143 | if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 { 144 | wallet := &keystoreWallet{account: account, keystore: ks} 145 | 146 | events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) 147 | wallets = append(wallets, wallet) 148 | continue 149 | } 150 | // If the account is the same as the first wallet, keep it 151 | if ks.wallets[0].Accounts()[0] == account { 152 | wallets = append(wallets, ks.wallets[0]) 153 | ks.wallets = ks.wallets[1:] 154 | continue 155 | } 156 | } 157 | // Drop any leftover wallets and set the new batch 158 | for _, wallet := range ks.wallets { 159 | events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) 160 | } 161 | ks.wallets = wallets 162 | ks.mu.Unlock() 163 | 164 | // Fire all wallet events and return 165 | for _, event := range events { 166 | ks.updateFeed.Send(event) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析以太坊新建以太坊账户过程.md: -------------------------------------------------------------------------------- 1 | 2 | ## 从命令行开始解析以太坊新建账户过程(`geth account new`命令) 3 | 4 | ![account](https://github.com/guoshijiang/go-ethereum-code-analysis/blob/master/cmd-process-analysis/img/1.png) 5 | 6 | 如上图是生成一个账户的过程,最后的账户表现为一个地址那么,这个过程的代码是怎么实现的呢,接下来我们从main函数命令行开始解析这个过程的代码。 7 | 8 | 在cmd/geth/main.go中的init函数中,有一个accountCommand的命令行参数 9 | 10 | func init() { 11 | // Initialize the CLI app and start Geth 12 | app.Action = geth 13 | app.HideVersion = true // we have a command to print the version 14 | app.Copyright = "Copyright 2013-2018 The go-ethereum Authors" 15 | app.Commands = []cli.Command{ 16 | // See chaincmd.go: 17 | initCommand, 18 | importCommand, 19 | exportCommand, 20 | importPreimagesCommand, 21 | exportPreimagesCommand, 22 | copydbCommand, 23 | removedbCommand, 24 | dumpCommand, 25 | // See monitorcmd.go: 26 | monitorCommand, 27 | // See accountcmd.go: 28 | accountCommand, 29 | walletCommand, 30 | // See consolecmd.go: 31 | consoleCommand, 32 | attachCommand, 33 | javascriptCommand, 34 | // See misccmd.go: 35 | makecacheCommand, 36 | makedagCommand, 37 | versionCommand, 38 | bugCommand, 39 | licenseCommand, 40 | // See config.go 41 | dumpConfigCommand, 42 | } 43 | sort.Sort(cli.CommandsByName(app.Commands)) 44 | 45 | app.Flags = append(app.Flags, nodeFlags...) 46 | app.Flags = append(app.Flags, rpcFlags...) 47 | app.Flags = append(app.Flags, consoleFlags...) 48 | app.Flags = append(app.Flags, debug.Flags...) 49 | app.Flags = append(app.Flags, whisperFlags...) 50 | 51 | app.Before = func(ctx *cli.Context) error { 52 | runtime.GOMAXPROCS(runtime.NumCPU()) 53 | if err := debug.Setup(ctx); err != nil { 54 | return err 55 | } 56 | // Start system runtime metrics collection 57 | go metrics.CollectProcessMetrics(3 * time.Second) 58 | 59 | utils.SetupNetwork(ctx) 60 | return nil 61 | } 62 | 63 | app.After = func(ctx *cli.Context) error { 64 | debug.Exit() 65 | console.Stdin.Close() // Resets terminal mode. 66 | return nil 67 | } 68 | } 69 | 70 | 账户相关的命令在cmd/geth/accountcmd.go里,新建账户命令为new,其会调accountCreate方法,我们继续看: 71 | 72 | accountCommand = cli.Command{ 73 | Name: "account", 74 | Usage: "Manage accounts", 75 | Category: "ACCOUNT COMMANDS", 76 |     77 | 子命令: 78 | 79 | { 80 | Name: "new", 81 | Usage: "Create a new account", 82 | Action: utils.MigrateFlags(accountCreate), 83 | Flags: []cli.Flag{ 84 | utils.DataDirFlag, 85 | utils.KeyStoreDirFlag, 86 | utils.PasswordFileFlag, 87 | utils.LightKDFFlag, 88 | }, 89 | Description: ` 90 | geth account new 91 | 92 | Creates a new account and prints the address. 93 | 94 | The account is saved in encrypted format, you are prompted for a passphrase. 95 | 96 | You must remember this passphrase to unlock your account in the future. 97 | 98 | For non-interactive use the passphrase can be specified with the --password flag: 99 | 100 | Note, this is meant to be used for testing only, it is a bad idea to save your 101 | password to file or expose in any other way. 102 | `, 103 | } 104 | 105 | 账户相关的命令在cmd/geth/accountcmd.go里,新建账户命令为new,其会调accountCreate方法,accountCreate在CLI标志定义的密钥库中创建一个新帐户 106 | 107 | func accountCreate(ctx *cli.Context) error { 108 | cfg := gethConfig{Node: defaultNodeConfig()} 109 | // Load config file. 110 | if file := ctx.GlobalString(configFileFlag.Name); file != "" { 111 | if err := loadConfig(file, &cfg); err != nil { 112 | utils.Fatalf("%v", err) 113 | } 114 | } 115 | utils.SetNodeConfig(ctx, &cfg.Node) 116 | scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 117 | 118 | if err != nil { 119 | utils.Fatalf("Failed to read configuration: %v", err) 120 | } 121 | 122 | password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 123 | 124 | address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 125 | 126 | if err != nil { 127 | utils.Fatalf("Failed to create account: %v", err) 128 | } 129 | fmt.Printf("Address: {%x}\n", address) 130 | return nil 131 | } 132 | 133 | 获取配置并解析用户的密码, StoreKey生成一个密钥,用'auth'加密并存储在给定的目录中 134 | 135 | func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) { 136 | _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth) 137 | return a.Address, err 138 | } 139 | 140 | storeNewKey()来创建一个新的账户,具体表现为生成一对公私钥,再由私钥算出地址并构建一个自定义的Key 141 | 142 | func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { 143 | key, err := newKey(rand) 144 | if err != nil { 145 | return nil, accounts.Account{}, err 146 | } 147 | a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}} 148 | if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { 149 | zeroKey(key.PrivateKey) 150 | return nil, a, err 151 | } 152 | return key, a, err 153 | } 154 | 155 |     156 | Key的生成函数,通过椭圆曲线加密生成的私钥,生成Key 157 | 158 | func newKey(rand io.Reader) (*Key, error) { 159 | privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) 160 | if err != nil { 161 | return nil, err 162 | } 163 | return newKeyFromECDSA(privateKeyECDSA), nil 164 | } 165 | 166 | 生成公钥和私钥对,`ecdsa.GenerateKey(crypto.S256(), rand)` 以太坊采用了椭圆曲线数字签名算法(ECDSA)生成一对公私钥,并选择的是secp256k1曲线 167 | 168 | func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { 169 | k, err := randFieldElement(c, rand) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | priv := new(PrivateKey) 175 | priv.PublicKey.Curve = c 176 | priv.D = k 177 | priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes()) 178 | return priv, nil 179 | } 180 | 181 | 182 | 以太坊使用私钥通过 ECDSA算法推导出公钥,继而经过 Keccak-256 单向散列函数推导出地址 183 | 184 | 185 | func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { 186 | id := uuid.NewRandom() 187 | key := &Key{ 188 | Id: id, 189 | Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), 190 | PrivateKey: privateKeyECDSA, 191 | } 192 | return key 193 | } 194 | 195 | func PubkeyToAddress(p ecdsa.PublicKey) common.Address { 196 | pubBytes := FromECDSAPub(&p) 197 | return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) 198 | } 199 | 200 | 地址代表以太坊帐户的20字节地址 201 | 202 | const ( 203 | HashLength = 32 204 | AddressLength = 20 205 | ) 206 | 207 | type Address [AddressLength]byte 208 | 209 | func BytesToAddress(b []byte) Address { 210 | var a Address 211 | a.SetBytes(b) 212 | return a 213 | } 214 | func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } 215 | func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } 216 |     217 | 总结,整个过程如下: 218 | 219 | * 创建随机私钥 (64 位 16 进制字符 / 32 字节) 220 | * 从私钥推导出公钥 (128 位 16 进制字符 / 64 字节) 221 | * 从公钥推导出地址 (40 位 16 进制字符 / 20 字节) 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析同步区块的代码分析.md: -------------------------------------------------------------------------------- 1 | 2 | # 从命令行开始解析同步区块的代码 3 | 4 | ## 一.同步简介 5 | 6 | 我们都知道geth支持三种同步模式 7 | 8 | * fast模式:从开始到结束,获取区块的header,获取区块的body,从创始块开始校验每一个元素,需要下载所有区块数据信息。速度最慢,但是能获取到所有的历史数据。 9 | 10 | * full模式:获取区块的header,获取区块的body,在同步到当前块之前不处理任何事务。然后获得一个快照,此后,像full节点一样进行后面的同步操作。这种方法用得最多,目的在不要在意历史数据,将历史数据按照快照的方式,不逐一验证,沿着区块下载最近数据库中的交易,有可能丢失历史数据。此方法可能会对历史数据有部分丢失,但是不影响今后的使用。 11 | 12 | * light模式:仅获取当前状态。验证元素需要向full节点发起相应的请求。 13 | 14 | ## 二.从命令行开始解析同步区块代码 15 | 16 | geth的main包配置函数utils.SyncModeFlag,在util包中可以看到Name和Usage参数 17 | 18 | 19 | nodeFlags = []cli.Flag{ 20 | utils.IdentityFlag, 21 | utils.UnlockedAccountFlag, 22 | utils.PasswordFileFlag, 23 | utils.BootnodesFlag, 24 | utils.BootnodesV4Flag, 25 | utils.BootnodesV5Flag, 26 | utils.DataDirFlag, 27 | utils.KeyStoreDirFlag, 28 | utils.NoUSBFlag, 29 | utils.DashboardEnabledFlag, 30 | utils.DashboardAddrFlag, 31 | utils.DashboardPortFlag, 32 | utils.DashboardRefreshFlag, 33 | utils.EthashCacheDirFlag, 34 | utils.EthashCachesInMemoryFlag, 35 | utils.EthashCachesOnDiskFlag, 36 | utils.EthashDatasetDirFlag, 37 | utils.EthashDatasetsInMemoryFlag, 38 | utils.EthashDatasetsOnDiskFlag, 39 | utils.TxPoolNoLocalsFlag, 40 | utils.TxPoolJournalFlag, 41 | utils.TxPoolRejournalFlag, 42 | utils.TxPoolPriceLimitFlag, 43 | utils.TxPoolPriceBumpFlag, 44 | utils.TxPoolAccountSlotsFlag, 45 | utils.TxPoolGlobalSlotsFlag, 46 | utils.TxPoolAccountQueueFlag, 47 | utils.TxPoolGlobalQueueFlag, 48 | utils.TxPoolLifetimeFlag, 49 | utils.FastSyncFlag, 50 | utils.LightModeFlag, 51 | utils.SyncModeFlag, 52 | utils.GCModeFlag, 53 | utils.LightServFlag, 54 | utils.LightPeersFlag, 55 | utils.LightKDFFlag, 56 | utils.CacheFlag, 57 | utils.CacheDatabaseFlag, 58 | utils.CacheGCFlag, 59 | utils.TrieCacheGenFlag, 60 | utils.ListenPortFlag, 61 | utils.MaxPeersFlag, 62 | utils.MaxPendingPeersFlag, 63 | utils.EtherbaseFlag, 64 | utils.GasPriceFlag, 65 | utils.MinerThreadsFlag, 66 | utils.MiningEnabledFlag, 67 | utils.TargetGasLimitFlag, 68 | utils.NATFlag, 69 | utils.NoDiscoverFlag, 70 | utils.DiscoveryV5Flag, 71 | utils.NetrestrictFlag, 72 | utils.NodeKeyFileFlag, 73 | utils.NodeKeyHexFlag, 74 | utils.DeveloperFlag, 75 | utils.DeveloperPeriodFlag, 76 | utils.TestnetFlag, 77 | utils.RinkebyFlag, 78 | utils.VMEnableDebugFlag, 79 | utils.NetworkIdFlag, 80 | utils.RPCCORSDomainFlag, 81 | utils.RPCVirtualHostsFlag, 82 | utils.EthStatsURLFlag, 83 | utils.MetricsEnabledFlag, 84 | utils.FakePoWFlag, 85 | utils.NoCompactionFlag, 86 | utils.GpoBlocksFlag, 87 | utils.GpoPercentileFlag, 88 | utils.ExtraDataFlag, 89 | configFileFlag, 90 | } 91 | 92 | 93 | flag配置详情 94 | 95 | defaultSyncMode = eth.DefaultConfig.SyncMode 96 | SyncModeFlag = TextMarshalerFlag{ 97 | Name: "syncmode", 98 | Usage: `Blockchain sync mode ("fast", "full", or "light")`, 99 | Value: &defaultSyncMode, 100 | } 101 | 102 | flag相关配置的结构体类型 103 | 104 | type TextMarshalerFlag struct { 105 | Name string 106 | Value TextMarshaler 107 | Usage string 108 | } 109 | 110 | TextMarshalerFlag操作的成员方法 111 | 112 | func (f TextMarshalerFlag) GetName() string { 113 | return f.Name 114 | } 115 | 116 | func (f TextMarshalerFlag) String() string { 117 | return fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage) 118 | } 119 | 120 | func (f TextMarshalerFlag) Apply(set *flag.FlagSet) { 121 | eachName(f.Name, func(name string) { 122 | set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage) 123 | }) 124 | } 125 | 126 | 从全局的flag配置中返回一个TextMarshalerFlag配置标志 127 | 128 | func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler { 129 | val := ctx.GlobalGeneric(name) 130 | if val == nil { 131 | return nil 132 | } 133 | return val.(textMarshalerVal).v 134 | } 135 | 136 | 默认配置使用主网络的代码 137 | 138 | var DefaultConfig = Config{ 139 | SyncMode: downloader.FastSync, 140 | Ethash: ethash.Config{ 141 | CacheDir: "ethash", 142 | CachesInMem: 2, 143 | CachesOnDisk: 3, 144 | DatasetsInMem: 1, 145 | DatasetsOnDisk: 2, 146 | }, 147 | NetworkId: 1, 148 | LightPeers: 100, 149 | DatabaseCache: 768, 150 | TrieCache: 256, 151 | TrieTimeout: 5 * time.Minute, 152 | GasPrice: big.NewInt(18 * params.Shannon), 153 | 154 | TxPool: core.DefaultTxPoolConfig, 155 | GPO: gasprice.Config{ 156 | Blocks: 20, 157 | Percentile: 60, 158 | }, 159 | } 160 | 161 | 下载模式配置的代码,本段代码存在于eth包中的config.go的config结构体内部 162 | 163 | SyncMode downloader.SyncMode 164 | 165 | 同步的类型是SyncMode,而SyncMode的真实类型是int。const常量的定义给不同模式分别赋值: 166 | 167 | * full:0 168 | * fast: 1 169 | * light: 2 170 | 171 | 172 | type SyncMode int 173 | 174 | const ( 175 | FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks 176 | FastSync // Quickly download the headers, full sync only at the chain head 177 | LightSync // Download only the headers and terminate afterwards 178 | ) 179 | 180 | 整个模式的变更代码请看downloader包中的modes.go 181 | 182 | 183 | 此方法比较简单,当传入的mode大于等于0并且小于等于2时返回true。可以简单理解为是一个合法性的校验 184 | 185 | func (mode SyncMode) IsValid() bool { 186 | return mode >= FullSync && mode <= LightSync 187 | } 188 | 189 | 此段代码实现了stringer的接口,当被调用时会返回对应的字符串描述:full,fast,light,unknown。此方法类似与Java中的toString方法。 190 | 191 | func (mode SyncMode) String() string { 192 | switch mode { 193 | case FullSync: 194 | return "full" 195 | case FastSync: 196 | return "fast" 197 | case LightSync: 198 | return "light" 199 | default: 200 | return "unknown" 201 | } 202 | } 203 | 204 | 此方法实现了encoding包下的TextMarshaler接口的MarshalText方法,根据传入的同步类型值返回字符串编码(UTF-8-encoded)之后的文本内容。可以简单理解为SyncMode(int)和文本内容的转换。 205 | 206 | func (mode SyncMode) MarshalText() ([]byte, error) { 207 | switch mode { 208 | case FullSync: 209 | return []byte("full"), nil 210 | case FastSync: 211 | return []byte("fast"), nil 212 | case LightSync: 213 | return []byte("light"), nil 214 | default: 215 | return nil, fmt.Errorf("unknown sync mode %d", mode) 216 | } 217 | } 218 | 219 | 此方法实现了encoding包下的TextUnmarshaler接口的UnmarshalText方法,根据传入的文本内容 220 | 返回SyncMode类型对应的值。可以简单理解为文本内容和SyncMode(int)的转换。 221 | 222 | func (mode *SyncMode) UnmarshalText(text []byte) error { 223 | switch string(text) { 224 | case "full": 225 | *mode = FullSync 226 | case "fast": 227 | *mode = FastSync 228 | case "light": 229 | *mode = LightSync 230 | default: 231 | return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text) 232 | } 233 | return nil 234 | } 235 | 236 | 同步模式中途的变更经过上面的代码分析我们是否就确定,如果不传递参数geth一直就是通过fast模式进行同步的么?那么,再看看下面的代码分析吧。 237 | 238 | 在eth/handler.go中方法NewProtocolManager中的代码: 239 | 240 | 241 | 242 | // Figure out whether to allow fast sync or not 243 | if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 { 244 | log.Warn("Blockchain not empty, fast sync disabled") 245 | mode = downloader.FullSync 246 | } 247 | if mode == downloader.FastSync { 248 | manager.fastSync = uint32(1) 249 | } 250 | 251 | 252 | 这段代码是在创建ProtocolManager时进行同步模式的参数设置。blockchain.CurrentBlock()获得当前的区块信息,NumberU64()返回的是最新区块的头部的number 253 | 254 | 255 | func (b *Block) NumberU64() uint64 { 256 | return b.header.Number.Uint64() 257 | } 258 | 259 | 260 | 现在整理一下这段代码的整体逻辑就是,当同步模式为fast并最新区块的高度大于0(已经同步过一部分数据)时,程序自动将同步模式转变为full,并打印警告信息。 261 | 262 | -------------------------------------------------------------------------------- /cmd-process-analysis/从命令行开始解析生成创世块源码.md: -------------------------------------------------------------------------------- 1 | 2 | # 创建创世块的代码解析 3 | 4 | 在cmd的geth目录下的main.go中 5 | 6 | init函数先于main函数执行,做命令的初始化,其中比较重要的有三个地方,app.Action=geth,app.Commands中consoleCommand,以及App.Before指向的匿名函数 7 | 8 | func init() { 9 | // Initialize the CLI app and start Geth 10 | app.Action = geth 11 | app.HideVersion = true // we have a command to print the version 12 | app.Copyright = "Copyright 2013-2018 The go-ethereum Authors" 13 | app.Commands = []cli.Command{ 14 | // See chaincmd.go: 15 | initCommand, 16 | importCommand, 17 | exportCommand, 18 | importPreimagesCommand, 19 | exportPreimagesCommand, 20 | copydbCommand, 21 | removedbCommand, 22 | dumpCommand, 23 | // See monitorcmd.go: 24 | monitorCommand, 25 | // See accountcmd.go: 26 | accountCommand, 27 | walletCommand, 28 | // See consolecmd.go: 29 | consoleCommand, 30 | attachCommand, 31 | javascriptCommand, 32 | // See misccmd.go: 33 | makecacheCommand, 34 | makedagCommand, 35 | versionCommand, 36 | bugCommand, 37 | licenseCommand, 38 | // See config.go 39 | dumpConfigCommand, 40 | } 41 | sort.Sort(cli.CommandsByName(app.Commands)) 42 | 43 | app.Flags = append(app.Flags, nodeFlags...) 44 | app.Flags = append(app.Flags, rpcFlags...) 45 | app.Flags = append(app.Flags, consoleFlags...) 46 | app.Flags = append(app.Flags, debug.Flags...) 47 | app.Flags = append(app.Flags, whisperFlags...) 48 | 49 | app.Before = func(ctx *cli.Context) error { 50 | runtime.GOMAXPROCS(runtime.NumCPU()) 51 | if err := debug.Setup(ctx); err != nil { 52 | return err 53 | } 54 | // Start system runtime metrics collection 55 | go metrics.CollectProcessMetrics(3 * time.Second) 56 | 57 | utils.SetupNetwork(ctx) 58 | return nil 59 | } 60 | 61 | app.After = func(ctx *cli.Context) error { 62 | debug.Exit() 63 | console.Stdin.Close() // Resets terminal mode. 64 | return nil 65 | } 66 | } 67 | 68 | main函数中的内容比较简单,这里不多做任何解释。 69 | 70 | func main() { 71 | if err := app.Run(os.Args); err != nil { 72 | fmt.Fprintln(os.Stderr, err) 73 | os.Exit(1) 74 | } 75 | } 76 | 77 | 通过gopkg.in/urfave/cli.v1包中的代码来衔接到cmd/geth/chaincmd包中的代码,APP包中中HandleAction函数action的类型是「func(*Context) error」,此时将执行a(context)方法,那么此时调用那个Action呢,就是我们前面提到的App.init()初始化命令时的initCommand,接下来我们来看看cmd/geth/chaincmd中的initCommand: 78 | 79 | var ( 80 | initCommand = cli.Command{ 81 | Action: utils.MigrateFlags(initGenesis), 82 | Name: "init", 83 | Usage: "Bootstrap and initialize a new genesis block", 84 | ArgsUsage: "", 85 | Flags: []cli.Flag{ 86 | utils.DataDirFlag, 87 | utils.LightModeFlag, 88 | }, 89 | Category: "BLOCKCHAIN COMMANDS", 90 | Description: ` 91 | The init command initializes a new genesis block and definition for the network. 92 | This is a destructive action and changes the network in which you will be 93 | participating. 94 | 95 | It expects the genesis file as argument.`, 96 | } 97 | 98 | 指定数据的存储目录命令行参数 --datadir的代码 99 | 100 | DataDirFlag = DirectoryFlag{ 101 | Name: "datadir", 102 | Usage: "Data directory for the databases and keystore", 103 | Value: DirectoryString{node.DefaultDataDir()}, 104 | } 105 |       106 | 当用户没有指定存储目录,下面代码是数据存储默认存放指定的代码: 107 | 108 | func DefaultDataDir() string { 109 | // Try to place the data folder in the user's home dir 110 | home := homeDir() 111 | if home != "" { 112 | if runtime.GOOS == "darwin" { 113 | return filepath.Join(home, "Library", "Ethereum") 114 | } else if runtime.GOOS == "windows" { 115 | return filepath.Join(home, "AppData", "Roaming", "Ethereum") 116 | } else { 117 | return filepath.Join(home, ".ethereum") 118 | } 119 | } 120 | // As we cannot guess a stable location, return empty and handle later 121 | return "" 122 | } 123 | 124 | 获取用户当前目录的地址 125 | 126 | func homeDir() string { 127 | if home := os.Getenv("HOME"); home != "" { 128 | return home 129 | } 130 | if usr, err := user.Current(); err == nil { 131 | return usr.HomeDir 132 | } 133 | return "" 134 | } 135 | 136 | 这允许使用现有的配置功能。当所有标志被迁移时,该功能可以被删除,并且必须改变现有的配置功能,即使用本地标志 137 | 138 | func MigrateFlags(action func(ctx *cli.Context) error) func(*cli.Context) error { 139 | return func(ctx *cli.Context) error { 140 | for _, name := range ctx.FlagNames() { 141 | if ctx.IsSet(name) { 142 | ctx.GlobalSet(name, ctx.String(name)) 143 | } 144 | } 145 | return action(ctx) 146 | } 147 | } 148 | 149 | 根据给定的JSon格式文件初始化创世块 150 | 151 | func initGenesis(ctx *cli.Context) error { 152 | // Make sure we have a valid genesis JSON 153 | genesisPath := ctx.Args().First() 154 | if len(genesisPath) == 0 { 155 | utils.Fatalf("Must supply path to genesis JSON file") 156 | } 157 | file, err := os.Open(genesisPath) 158 | if err != nil { 159 | utils.Fatalf("Failed to read genesis file: %v", err) 160 | } 161 | defer file.Close() 162 | 163 | genesis := new(core.Genesis) 164 | if err := json.NewDecoder(file).Decode(genesis); err != nil { 165 | utils.Fatalf("invalid genesis file: %v", err) 166 | } 167 | // Open an initialise both full and light databases 168 | stack := makeFullNode(ctx) 169 | for _, name := range []string{"chaindata", "lightchaindata"} { 170 | chaindb, err := stack.OpenDatabase(name, 0, 0) 171 | if err != nil { 172 | utils.Fatalf("Failed to open database: %v", err) 173 | } 174 | _, hash, err := core.SetupGenesisBlock(chaindb, genesis) 175 | if err != nil { 176 | utils.Fatalf("Failed to write genesis block: %v", err) 177 | } 178 | log.Info("Successfully wrote genesis state", "database", name, "hash", hash) 179 | } 180 | return nil 181 | } 182 | 183 | 将初始创世块存储 184 | 185 | chaindb, err := stack.OpenDatabase(name, 0, 0) 186 | if err != nil { 187 | utils.Fatalf("Failed to open database: %v", err) 188 | } 189 | 190 | 涉及到的Core层次的代码 191 | 192 | genesis := new(core.Genesis) 193 | 194 | _, hash, err := core.SetupGenesisBlock(chaindb, genesis) 195 | if err != nil { 196 | utils.Fatalf("Failed to write genesis block: %v", err) 197 | } 198 | 199 | 200 | 创世块的数据结构 201 | 202 | type Genesis struct { 203 | Config *params.ChainConfig `json:"config"` 204 | Nonce uint64 `json:"nonce"` 205 | Timestamp uint64 `json:"timestamp"` 206 | ExtraData []byte `json:"extraData"` 207 | GasLimit uint64 `json:"gasLimit" gencodec:"required"` 208 | Difficulty *big.Int `json:"difficulty" gencodec:"required"` 209 | Mixhash common.Hash `json:"mixHash"` 210 | Coinbase common.Address `json:"coinbase"` 211 | Alloc GenesisAlloc `json:"alloc" gencodec:"required"` 212 | 213 | // These fields are used for consensus tests. Please don't use them 214 | // in actual genesis blocks. 215 | Number uint64 `json:"number"` 216 | GasUsed uint64 `json:"gasUsed"` 217 | ParentHash common.Hash `json:"parentHash"` 218 | } 219 | 220 | 检查创世块的相关处理逻辑,符合逻辑写创世块,或者反馈控制台相应的问题 221 | 222 | func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { 223 | if genesis != nil && genesis.Config == nil { 224 | return params.AllProtocolChanges, common.Hash{}, errGenesisNoConfig 225 | } 226 | 227 | // Just commit the new block if there is no stored genesis block. 228 | stored := GetCanonicalHash(db, 0) //获取genesis对应的区块 229 | if (stored == common.Hash{}) { //如果没有区块 最开始启动geth会进入这里。 230 | if genesis == nil { 231 | //如果genesis是nil 而且stored也是nil 那么使用主网络 232 | // 如果是test dev rinkeby 那么genesis不为空 会设置为各自的genesis 233 | log.Info("Writing default main-net genesis block") 234 | genesis = DefaultGenesisBlock() 235 | } else { // 否则使用配置的区块 236 | log.Info("Writing custom genesis block") 237 | } 238 | // 写入数据库 239 | block, err := genesis.Commit(db) 240 | return genesis.Config, block.Hash(), err 241 | } 242 | 243 | // Check whether the genesis block is already written. 244 | if genesis != nil { //如果genesis存在而且区块也存在 那么对比这两个区块是否相同 245 | block, _ := genesis.ToBlock() 246 | hash := block.Hash() 247 | if hash != stored { 248 | return genesis.Config, block.Hash(), &GenesisMismatchError{stored, hash} 249 | } 250 | } 251 | 252 | // Get the existing chain configuration. 253 | // 获取当前存在的区块链的genesis配置 254 | newcfg := genesis.configOrDefault(stored) 255 | // 获取当前的区块链的配置 256 | storedcfg, err := GetChainConfig(db, stored) 257 | if err != nil { 258 | if err == ErrChainConfigNotFound { 259 | // This case happens if a genesis write was interrupted. 260 | log.Warn("Found genesis block without chain config") 261 | err = WriteChainConfig(db, stored, newcfg) 262 | } 263 | return newcfg, stored, err 264 | } 265 | // Special case: don't change the existing config of a non-mainnet chain if no new 266 | // config is supplied. These chains would get AllProtocolChanges (and a compat error) 267 | // if we just continued here. 268 | // 特殊情况:如果没有提供新的配置,请不要更改非主网链的现有配置。 269 | // 如果我们继续这里,这些链会得到AllProtocolChanges(和compat错误)。 270 | if genesis == nil && stored != params.MainnetGenesisHash { 271 | return storedcfg, stored, nil // 如果是私有链接会从这里退出。 272 | } 273 | 274 | // Check config compatibility and write the config. Compatibility errors 275 | // are returned to the caller unless we're already at block zero. 276 | // 检查配置的兼容性,除非我们在区块0,否则返回兼容性错误. 277 | height := GetBlockNumber(db, GetHeadHeaderHash(db)) 278 | if height == missingNumber { 279 | return newcfg, stored, fmt.Errorf("missing block number for head header hash") 280 | } 281 | compatErr := storedcfg.CheckCompatible(newcfg, height) 282 | // 如果区块已经写入数据了,那么就不能更改genesis配置了 283 | if compatErr != nil && height != 0 && compatErr.RewindTo != 0 { 284 | return newcfg, stored, compatErr 285 | } 286 | // 如果是主网络会从这里退出。 287 | return newcfg, stored, WriteChainConfig(db, stored, newcfg) 288 | } 289 | 290 | 291 | ToBlock, 这个方法使用genesis的数据,使用基于内存的数据库,然后创建了一个block并返回。 292 | 293 | // ToBlock creates the block and state of a genesis specification. 294 | func (g *Genesis) ToBlock() (*types.Block, *state.StateDB) { 295 | db, _ := ethdb.NewMemDatabase() 296 | statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) 297 | for addr, account := range g.Alloc { 298 | statedb.AddBalance(addr, account.Balance) 299 | statedb.SetCode(addr, account.Code) 300 | statedb.SetNonce(addr, account.Nonce) 301 | for key, value := range account.Storage { 302 | statedb.SetState(addr, key, value) 303 | } 304 | } 305 | root := statedb.IntermediateRoot(false) 306 | head := &types.Header{ 307 | Number: new(big.Int).SetUint64(g.Number), 308 | Nonce: types.EncodeNonce(g.Nonce), 309 | Time: new(big.Int).SetUint64(g.Timestamp), 310 | ParentHash: g.ParentHash, 311 | Extra: g.ExtraData, 312 | GasLimit: new(big.Int).SetUint64(g.GasLimit), 313 | GasUsed: new(big.Int).SetUint64(g.GasUsed), 314 | Difficulty: g.Difficulty, 315 | MixDigest: g.Mixhash, 316 | Coinbase: g.Coinbase, 317 | Root: root, 318 | } 319 | if g.GasLimit == 0 { 320 | head.GasLimit = params.GenesisGasLimit 321 | } 322 | if g.Difficulty == nil { 323 | head.Difficulty = params.GenesisDifficulty 324 | } 325 | return types.NewBlock(head, nil, nil, nil), statedb 326 | } 327 | 328 | 329 | 330 | Commit方法和MustCommit方法, Commit方法把给定的genesis的block和state写入数据库, 这个block被认为是规范的区块链头。 331 | 332 | // Commit writes the block and state of a genesis specification to the database. 333 | // The block is committed as the canonical head block. 334 | func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { 335 | block, statedb := g.ToBlock() 336 | if block.Number().Sign() != 0 { 337 | return nil, fmt.Errorf("can't commit genesis block with number > 0") 338 | } 339 | if _, err := statedb.CommitTo(db, false); err != nil { 340 | return nil, fmt.Errorf("cannot write state: %v", err) 341 | } 342 | // 写入总难度 343 | if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil { 344 | return nil, err 345 | } 346 | // 写入区块 347 | if err := WriteBlock(db, block); err != nil { 348 | return nil, err 349 | } 350 | // 写入区块收据 351 | if err := WriteBlockReceipts(db, block.Hash(), block.NumberU64(), nil); err != nil { 352 | return nil, err 353 | } 354 | // 写入 headerPrefix + num (uint64 big endian) + numSuffix -> hash 355 | if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil { 356 | return nil, err 357 | } 358 | // 写入 "LastBlock" -> hash 359 | if err := WriteHeadBlockHash(db, block.Hash()); err != nil { 360 | return nil, err 361 | } 362 | // 写入 "LastHeader" -> hash 363 | if err := WriteHeadHeaderHash(db, block.Hash()); err != nil { 364 | return nil, err 365 | } 366 | config := g.Config 367 | if config == nil { 368 | config = params.AllProtocolChanges 369 | } 370 | // 写入 ethereum-config-hash -> config 371 | return block, WriteChainConfig(db, block.Hash(), config) 372 | } 373 | 374 | // MustCommit writes the genesis block and state to db, panicking on error. 375 | // The block is committed as the canonical head block. 376 | func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { 377 | block, err := g.Commit(db) 378 | if err != nil { 379 | panic(err) 380 | } 381 | return block 382 | } 383 | 384 | 返回各种模式的默认Genesis 385 | 386 | // DefaultGenesisBlock returns the Ethereum main net genesis block. 387 | func DefaultGenesisBlock() *Genesis { 388 | return &Genesis{ 389 | Config: params.MainnetChainConfig, 390 | Nonce: 66, 391 | ExtraData: hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"), 392 | GasLimit: 5000, 393 | Difficulty: big.NewInt(17179869184), 394 | Alloc: decodePrealloc(mainnetAllocData), 395 | } 396 | } 397 | 398 | // DefaultTestnetGenesisBlock returns the Ropsten network genesis block. 399 | func DefaultTestnetGenesisBlock() *Genesis { 400 | return &Genesis{ 401 | Config: params.TestnetChainConfig, 402 | Nonce: 66, 403 | ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"), 404 | GasLimit: 16777216, 405 | Difficulty: big.NewInt(1048576), 406 | Alloc: decodePrealloc(testnetAllocData), 407 | } 408 | } 409 | 410 | // DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block. 411 | func DefaultRinkebyGenesisBlock() *Genesis { 412 | return &Genesis{ 413 | Config: params.RinkebyChainConfig, 414 | Timestamp: 1492009146, 415 | ExtraData: hexutil.MustDecode("0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 416 | GasLimit: 4700000, 417 | Difficulty: big.NewInt(1), 418 | Alloc: decodePrealloc(rinkebyAllocData), 419 | } 420 | } 421 | 422 | // DevGenesisBlock returns the 'geth --dev' genesis block. 423 | func DevGenesisBlock() *Genesis { 424 | return &Genesis{ 425 | Config: params.AllProtocolChanges, 426 | Nonce: 42, 427 | GasLimit: 4712388, 428 | Difficulty: big.NewInt(131072), 429 | Alloc: decodePrealloc(devAllocData), 430 | } 431 | } 432 | 433 |  由于代码分析者水平有限,如果其中有问题,欢迎大家一起交流 434 | 435 | -------------------------------------------------------------------------------- /cmd-process-analysis/具体的RPC-HTTP接口请求实例.md: -------------------------------------------------------------------------------- 1 | # 具体的RPC-HTTP接口请求实例 2 | 3 | 4 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/README.md: -------------------------------------------------------------------------------- 1 | # 按流程和文件目录分析以太坊源码 2 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/accounts/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/bmt/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/build/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/cmd/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 以太坊命令目录 3 | 4 | 本部分集成了以太坊所有geth相关的命令行 5 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/cmd/geth/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dicorfile-process-analysis/cmd/geth/accountcmd.go.md: -------------------------------------------------------------------------------- 1 | # 账户相关的命令行参数的Name和Usage,还命令行的作用描述 2 | 3 | 4 | 钱包命令行 5 | 6 | var ( 7 | walletCommand = cli.Command{ 8 | Name: "wallet", 9 | Usage: "Manage Ethereum presale wallets", 10 | ArgsUsage: "", 11 | Category: "ACCOUNT COMMANDS", 12 | Description: ` 13 | linkchain wallet import /path/to/my/presale.wallet 14 | 15 | will prompt for your password and imports your ether presale account. 16 | It can be used non-interactively with the --password option taking a 17 | passwordfile as argument containing the wallet password in plaintext.`, 18 | Subcommands: []cli.Command{ 19 | { 20 | 21 | Name: "import", 22 | Usage: "Import Ethereum presale wallet", 23 | ArgsUsage: "", 24 | Action: utils.MigrateFlags(importWallet), 25 | Category: "ACCOUNT COMMANDS", 26 | Flags: []cli.Flag{ 27 | utils.DataDirFlag, 28 | utils.KeyStoreDirFlag, 29 | utils.PasswordFileFlag, 30 | utils.LightKDFFlag, 31 | }, 32 | Description: ` 33 | linkchain wallet [options] /path/to/my/presale.wallet 34 | 35 | will prompt for your password and imports your ether presale account. 36 | It can be used non-interactively with the --password option taking a 37 | passwordfile as argument containing the wallet password in plaintext.`, 38 | }, 39 | }, 40 | } 41 | 42 | 账户相关的命令行,包括新建账户,更新账户,列出账户等命令行,大家可以仔细看看,这里的代码比较简单,这里不再做详细概述 43 | 44 | accountCommand = cli.Command{ 45 | Name: "account", 46 | Usage: "Manage accounts", 47 | Category: "ACCOUNT COMMANDS", 48 | Description: ` 49 | 50 | Manage accounts, list all existing accounts, import a private key into a new 51 | account, create a new account or update an existing account. 52 | 53 | It supports interactive mode, when you are prompted for password as well as 54 | non-interactive mode where passwords are supplied via a given password file. 55 | Non-interactive mode is only meant for scripted use on test networks or known 56 | safe environments. 57 | 58 | Make sure you remember the password you gave when creating a new account (with 59 | either new or import). Without it you are not able to unlock your account. 60 | 61 | Note that exporting your key in unencrypted format is NOT supported. 62 | 63 | Keys are stored under /keystore. 64 | It is safe to transfer the entire directory or the individual keys therein 65 | between ethereum nodes by simply copying. 66 | 67 | Make sure you backup your keys regularly.`, 68 | Subcommands: []cli.Command{ 69 | { 70 | Name: "list", 71 | Usage: "Print summary of existing accounts", 72 | Action: utils.MigrateFlags(accountList), 73 | Flags: []cli.Flag{ 74 | utils.DataDirFlag, 75 | utils.KeyStoreDirFlag, 76 | }, 77 | Description: ` 78 | Print a short summary of all accounts`, 79 | }, 80 | { 81 | Name: "new", 82 | Usage: "Create a new account", 83 | Action: utils.MigrateFlags(accountCreate), 84 | Flags: []cli.Flag{ 85 | utils.DataDirFlag, 86 | utils.KeyStoreDirFlag, 87 | utils.PasswordFileFlag, 88 | utils.LightKDFFlag, 89 | }, 90 | Description: ` 91 | linkchain account new 92 | 93 | Creates a new account and prints the address. 94 | 95 | The account is saved in encrypted format, you are prompted for a passphrase. 96 | 97 | You must remember this passphrase to unlock your account in the future. 98 | 99 | For non-interactive use the passphrase can be specified with the --password flag: 100 | 101 | Note, this is meant to be used for testing only, it is a bad idea to save your 102 | password to file or expose in any other way. 103 | `, 104 | }, 105 | { 106 | Name: "update", 107 | Usage: "Update an existing account", 108 | Action: utils.MigrateFlags(accountUpdate), 109 | ArgsUsage: "
", 110 | Flags: []cli.Flag{ 111 | utils.DataDirFlag, 112 | utils.KeyStoreDirFlag, 113 | utils.LightKDFFlag, 114 | }, 115 | Description: ` 116 | linkchain account update
117 | 118 | Update an existing account. 119 | 120 | The account is saved in the newest version in encrypted format, you are prompted 121 | for a passphrase to unlock the account and another to save the updated file. 122 | 123 | This same command can therefore be used to migrate an account of a deprecated 124 | format to the newest format or change the password for an account. 125 | 126 | For non-interactive use the passphrase can be specified with the --password flag: 127 | 128 | linkchain account update [options]
129 | 130 | Since only one password can be given, only format update can be performed, 131 | changing your password is only possible interactively. 132 | `, 133 | }, 134 | { 135 | Name: "import", 136 | Usage: "Import a private key into a new account", 137 | Action: utils.MigrateFlags(accountImport), 138 | Flags: []cli.Flag{ 139 | utils.DataDirFlag, 140 | utils.KeyStoreDirFlag, 141 | utils.PasswordFileFlag, 142 | utils.LightKDFFlag, 143 | }, 144 | ArgsUsage: "", 145 | Description: ` 146 | linkchain account import 147 | 148 | Imports an unencrypted private key from and creates a new account. 149 | Prints the address. 150 | 151 | The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 152 | 153 | The account is saved in encrypted format, you are prompted for a passphrase. 154 | 155 | You must remember this passphrase to unlock your account in the future. 156 | 157 | For non-interactive use the passphrase can be specified with the -password flag: 158 | 159 | linkchain account import [options] 160 | 161 | Note: 162 | As you can directly copy your encrypted accounts to another ethereum instance, 163 | this import mechanism is not needed when you transfer an account between 164 | nodes. 165 | `, 166 | }, 167 | }, 168 | } 169 | ) 170 | 171 | 172 | 执行`geth accoount list`命令的时候执行的函数,主要功能是列出账户的地址,在以太坊中,账户号其实就是一个地址 173 | 174 | 175 | func accountList(ctx *cli.Context) error { 176 | stack, _ := makeConfigNode(ctx) 177 | var index int 178 | for _, wallet := range stack.AccountManager().Wallets() { 179 | for _, account := range wallet.Accounts() { 180 | fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 181 | index++ 182 | } 183 | } 184 | return nil 185 | } 186 | 187 | 188 | 尝试多次解锁账户 189 | 190 | func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 191 | account, err := utils.MakeAddress(ks, address) 192 | if err != nil { 193 | utils.Fatalf("Could not list accounts: %v", err) 194 | } 195 | for trials := 0; trials < 3; trials++ { 196 | prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 197 | password := getPassPhrase(prompt, false, i, passwords) 198 | err = ks.Unlock(account, password) 199 | if err == nil { 200 | log.Info("Unlocked account", "address", account.Address.Hex()) 201 | return account, password 202 | } 203 | if err, ok := err.(*keystore.AmbiguousAddrError); ok { 204 | log.Info("Unlocked account", "address", account.Address.Hex()) 205 | return ambiguousAddrRecovery(ks, err, password), password 206 | } 207 | if err != keystore.ErrDecrypt { 208 | // No need to prompt again if the error is not decryption-related. 209 | break 210 | } 211 | } 212 | // All trials expended to unlock account, bail out 213 | utils.Fatalf("Failed to unlock account %s (%v)", address, err) 214 | 215 | return accounts.Account{}, "" 216 | } 217 | 218 | 检索与账户相关的密码,查看预加载的密码与账户的密码是否一致 219 | 220 | 221 |        func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { 222 | // If a list of passwords was supplied, retrieve from them 223 | if len(passwords) > 0 { 224 | if i < len(passwords) { 225 | return passwords[i] 226 | } 227 | return passwords[len(passwords)-1] 228 | } 229 | // Otherwise prompt the user for the password 230 | if prompt != "" { 231 | fmt.Println(prompt) 232 | } 233 | password, err := console.Stdin.PromptPassword("Passphrase: ") 234 | if err != nil { 235 | utils.Fatalf("Failed to read passphrase: %v", err) 236 | } 237 | if confirmation { 238 | confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 239 | if err != nil { 240 | utils.Fatalf("Failed to read passphrase confirmation: %v", err) 241 | } 242 | if password != confirm { 243 | utils.Fatalf("Passphrases do not match") 244 | } 245 | } 246 | return password 247 | } 248 | 249 | func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 250 | fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 251 | for _, a := range err.Matches { 252 | fmt.Println(" ", a.URL) 253 | } 254 | fmt.Println("Testing your passphrase against all of them...") 255 | var match *accounts.Account 256 | for _, a := range err.Matches { 257 | if err := ks.Unlock(a, auth); err == nil { 258 | match = &a 259 | break 260 | } 261 | } 262 | if match == nil { 263 | utils.Fatalf("None of the listed files could be unlocked.") 264 | } 265 | fmt.Printf("Your passphrase unlocked %s\n", match.URL) 266 | fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 267 | for _, a := range err.Matches { 268 | if a != *match { 269 | fmt.Println(" ", a.URL) 270 | } 271 | } 272 | return *match 273 | } 274 | 275 | 通过命令行标志`get account new`调用改函数新建一账户 276 | 277 |        func accountCreate(ctx *cli.Context) error { 278 | cfg := gethConfig{Node: defaultNodeConfig()} 279 | // Load config file. 280 | if file := ctx.GlobalString(configFileFlag.Name); file != "" { 281 | if err := loadConfig(file, &cfg); err != nil { 282 | utils.Fatalf("%v", err) 283 | } 284 | } 285 | utils.SetNodeConfig(ctx, &cfg.Node) 286 | scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 287 | 288 | if err != nil { 289 | utils.Fatalf("Failed to read configuration: %v", err) 290 | } 291 | 292 | password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 293 | 294 | address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 295 | 296 | if err != nil { 297 | utils.Fatalf("Failed to create account: %v", err) 298 | } 299 | fmt.Printf("Address: {%x}\n", address) 300 | return nil 301 | } 302 | 303 | 304 | 通过命令行`geth account update`调用来更新账户 305 | 306 | func accountUpdate(ctx *cli.Context) error { 307 | if len(ctx.Args()) == 0 { 308 | utils.Fatalf("No accounts specified to update") 309 | } 310 | stack, _ := makeConfigNode(ctx) 311 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 312 | 313 | for _, addr := range ctx.Args() { 314 | account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) 315 | newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 316 | if err := ks.Update(account, oldPassword, newPassword); err != nil { 317 | utils.Fatalf("Could not update the account: %v", err) 318 | } 319 | } 320 | return nil 321 | } 322 | 323 | func importWallet(ctx *cli.Context) error { 324 | keyfile := ctx.Args().First() 325 | if len(keyfile) == 0 { 326 | utils.Fatalf("keyfile must be given as argument") 327 | } 328 | keyJson, err := ioutil.ReadFile(keyfile) 329 | if err != nil { 330 | utils.Fatalf("Could not read wallet file: %v", err) 331 | } 332 | 333 | stack, _ := makeConfigNode(ctx) 334 | passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) 335 | 336 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 337 | acct, err := ks.ImportPreSaleKey(keyJson, passphrase) 338 | if err != nil { 339 | utils.Fatalf("%v", err) 340 | } 341 | fmt.Printf("Address: {%x}\n", acct.Address) 342 | return nil 343 | } 344 | 345 | 通过命令行`geth account import`引入私钥文件对账户进行相关的操作 346 | 347 | func accountImport(ctx *cli.Context) error { 348 | keyfile := ctx.Args().First() 349 | if len(keyfile) == 0 { 350 | utils.Fatalf("keyfile must be given as argument") 351 | } 352 | key, err := crypto.LoadECDSA(keyfile) 353 | if err != nil { 354 | utils.Fatalf("Failed to load the private key: %v", err) 355 | } 356 | stack, _ := makeConfigNode(ctx) 357 | passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 358 | 359 | ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 360 | acct, err := ks.ImportECDSA(key, passphrase) 361 | if err != nil { 362 | utils.Fatalf("Could not create the account: %v", err) 363 | } 364 | fmt.Printf("Address: {%x}\n", acct.Address) 365 | return nil 366 | } 367 | -------------------------------------------------------------------------------- /theory/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /theory/UTXO.md: -------------------------------------------------------------------------------- 1 | 2 | # 深入浅出讲解UTXO模型和账户模型 3 | 4 | 目前的区块链技术中,记账的方式主要有两种形式,以比特币为代表的UTXO模型,UTXO是该记账模式的缩写,它的英文全拼为Unspent Transaction Output,汉语翻译为“未花费的交易输出”;对于“未花费的交易输出”这个名词对于大多数人来说,应该会比较陌生。如果你觉得陌生的话,请接着文章下面的分解。另一种是以以太坊为代表的基于账户/余额的记账模式,基于账户/余额的这种记账模式比较简单了,和现在的银行卡记账方式一样,大多数人都能够很清晰地理解这种模型。 5 | 6 | ### 一.解说UTXO模型 7 | 8 | 在解说UTXO模型之前,我想先说说一个名词,叫做Transaction,即交易,Transaction和UTXO是相辅相成的,下面先来举个例子: 9 | 10 | 张三:通过挖矿得到了120个比特币,现在张三饿了,想要花费2个比特币去购买一个面包。这是只是举例而已,以现在比特币的价格,1个比特币就可以买到一大堆面包了。 11 | 12 | 现在我们假设,李四是面包点营业主 13 | 14 | 这样的话,在比特币中,张三付给李四将一次性付给李四120块,李四给张三找零118块,这里其实产生了两笔交易,咱们可以这样理解,张三的钱被分成了两份,其中118打给了自己,剩下的2块打给了李四。 15 | 16 | 交易与交易之间组成了网状关系,1个交易的输出,成为了下1个交易的输入;下1个交易的输出,又成了下下1个交易的输入。所有的钱,在这个网络中流动,每1笔钱的去向、来源,都是可追溯的,而这也是区块链网络的一个重要特点。 17 | 18 | 上面的讲解可能大多数人都比较懵逼,接下来咱们用比较通俗的方式来说明UTXO和Transaction到底是什么 19 | 20 | 在现实生活中,一笔转账对应的事一个付款人和一个收款人,而在比特币种,一笔转账对应的事多个转账人和多个收款人。 21 | 22 | 现在咱们仔细分析一下上面的这个例子,对于张三买面包这个案列 23 | 24 | 付款人:张三 120块 25 | 收款人:张三 118块,李四 2块 26 | 27 | 张三的120,转118块给自己,转2块给李四,对应到交易里面,就是这笔交易有1个输入,2个输出! 28 | 29 | 下面来一个多输入,多输出的案列 30 | 31 | 考虑如下场景:用户A和用户B之间发生了一个交易T3,A向B转100元。 32 | A的100元,来自T1:C向A转的80元 + T2:D向A转的30元(共110元,但A只转了100元给B,10元找零返回给A的账号)。 33 | 同理,C向A转的这80元,来自用户E、F的某次交易...... 34 | D向A转的这30元,来自用户E的某次交易...... 35 | 36 | 这个交易就有2个输入,2个输出: 37 | 2个输入(也就是2个UTXO): 38 | T1: C向A转的80元 39 | T2:D向A转的30元 40 | 41 | 2个输出: 42 | B:100元 43 | A:10元(找零) 44 | 45 | 当你理解上面的例子时,我们再来说一下UTXO,理解上面的例子对你理解UTXO会特别有帮助。 46 | 47 | 1.比特币的交易中不是通过账户的增减来实现的,而是一笔笔关联的输入/输出交易事务。 48 | 49 | 2.每一笔的交易都要花费“输入”,然后产生“输出”,这个产生的“输出”就是所谓的“未花费过的交易输出”,也就是UTXO。每一笔交易事务都有一个唯一的编号,称为交易事务ID,这是通过哈希算法计算而来的,当需要引用某一笔交易事务中的“输出”时,主要提供交易事务ID和所处“输出”列表中的序号就可以了。 50 | 51 | 3.由于没有账户的概念,因此当“输入”部分的金额大于所需的“输出”时,必须给自己找零,这个找零也是作为交易的一部分包含在“输出”中。 52 | 53 | 4.旧的UTXO不断消亡,新的UTXO不断产生。所有的UTXO,组成了UTXO Set 的数据库,存在于每个节点 54 | 55 | 5.任何1笔UTXO,有且仅可能被1个交易花费1次 56 | 57 | 6.1个UTXO,具有如下的表达形式: 58 | 1个UTXO = 1个Transaction ID + Output Index 59 | 60 | ### 二.UTXO模型的区块链钱包余额形式 61 | 62 | 深刻理解了UTXO的概念,钱包就很容易理解了,某个人的钱包的余额 = 属于他的UTXO的总和;在这里,你会发现一个不同于现实世界的“银行”里的一个概念,在银行里,会存储每个账号剩余多少钱。但这里,我们存储的并不是每个账号的余额,而存的是1笔笔的交易,也就是1笔笔的UTXO,每个账户的余额是通过UTXO计算出来的,而不是直接存储余额。 63 | 64 | 65 | ### 三.账户余额模型 66 | 67 | 账户余额模型和当今的银行卡一样,当我们需要花费钱的时候,会去先检查我们的余额是否足够。转账的整个业务流程和银行卡一样的,基于账户的余额。 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /theory/比特币交易签名过程.md: -------------------------------------------------------------------------------- 1 | # 比特币交易签名过程 2 | -------------------------------------------------------------------------------- /wallet/NodeJs实现HD分层钱包的账户体系和转账签名.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wallet/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## linkeye的钱包源码分析 3 | 4 | * linkeye钱包项目github地址:https://github.com/linkeye/linkeye-wallet 5 | 6 | ### linkeye钱包项目依赖 7 | 8 | * linkeyejs-tx的github地址:https://github.com/linkeye/linkeyejs-tx 9 | * keylinkeye的github地址:https://github.com/linkeye/keylinkeye 10 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/img/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/img/linkeye-wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoshijiang/blockchain/2844a231d3fe185d33ec697a4c6bb9382ddf9afe/wallet/linkeye-wallet/img/linkeye-wallet.png -------------------------------------------------------------------------------- /wallet/linkeye-wallet/linkeye钱包项目架构分析.md: -------------------------------------------------------------------------------- 1 | 2 | # linkeye钱包项目架构分析 3 | 4 | ### 一.简述 5 | 6 | linkeye钱包是对接linkeye公链的钱包项目,不能对接其他的公链。linkeye钱包是nodejs, electron和vue开发的本地桌面钱包,整个账户体系的信息都在用户自己的设备上。nodeJs是一个本地的服务,vue是渲染进程,nodejs本地服务和vue渲染进程之间通过electron的主进程和渲染进程通信机制通信。用户的账户体系数据存储在本地sqlite3数据库中。整个项目分为创建账户生成keystore模块,导出私钥模块,导入私钥模块,账户体系模块,转账记录模块,转账模块,转账确认模块。 7 | 8 | 9 | linkeye-wallet架构图: 10 | ![linkeye-wallet架构图: 11 | ](https://github.com/guoshijiang/go-ethereum-code-analysis/blob/master/wallet/linkeye-wallet/img/linkeye-wallet.png "linkeye-wallet架构图: 12 | ") 13 | 14 | 本项目中把IPC通信的机制封装了,渲染进程的封装代码如下: 15 | 16 | 17 | const { ipcRenderer } = require('electron') 18 | const { 19 | CLIENT_NORMAL_MSG, 20 | CRAWLER_NORMAL_MSG, 21 | } = require('./../../constants/constants') 22 | 23 | const ipcService = Object.create(null) 24 | const callbackCache = [] 25 | 26 | ipcService.install = Vue => { 27 | Vue.prototype.$ipcRenderer = { 28 | send: (msgType, msgData) => { 29 | ipcRenderer.send(CLIENT_NORMAL_MSG, { 30 | type: msgType, 31 | data: msgData, 32 | }) 33 | }, 34 | on: (type, callback) => { 35 | callbackCache.push({ 36 | type, 37 | callback, 38 | }) 39 | }, 40 | detach: type => { 41 | const idx = callbackCache.findIndex(v => v.type === type) 42 | idx > -1 ? callbackCache.splice(idx, 1) : console.error(`error type ${type}`) 43 | }, 44 | } 45 | ipcRenderer.on(CRAWLER_NORMAL_MSG, (sender, msg) => { 46 | callbackCache.forEach(cache => { 47 | if (cache.type === msg.type) { 48 | cache.callback && cache.callback(msg.data) 49 | } 50 | }) 51 | }) 52 | } 53 | 54 | export default ipcService 55 | 56 | 57 | 以上代码中将调用的事件放到一个事件队列中(其实是一个数组),当事件调用完成后,detach一下,就可以把事件从数组中删除了。 58 | 59 | 在node服务,每个接口请求linkeye钱包针对请求封装了一个IPC通信的类,代码都比较类似,我们这里就讲其中的一个IPC类 60 | 61 | import queryBlock from '../block/queryBlock' 62 | 63 | const {CLIENT_NORMAL_MSG, CRAWLER_NORMAL_MSG,} = require('../../constants/constants') 64 | 65 | export default class queryBlockIpc { 66 | constructor(listener, sender) { 67 | this.listener = listener 68 | this.sender = sender 69 | this.addListener(CLIENT_NORMAL_MSG, this.handleFn.bind(this)) 70 | this.handlerQueryBlockIpc = queryBlock(this) 71 | } 72 | 73 | handleFn(event, data) { 74 | try { 75 | this.handlerQueryBlockIpc[data.type](event, data.data) 76 | } catch (error) { 77 | console.error('handler event error:' + error.message) 78 | } 79 | } 80 | 81 | addListener(chanel, cb) { 82 | this.listener.on(chanel, cb) 83 | } 84 | 85 | _sendMsg(chanel, msgBody) { 86 | this.sender.send(chanel, msgBody) 87 | } 88 | 89 | sendToClient(type, data) { 90 | this._sendMsg(CRAWLER_NORMAL_MSG, { 91 | type, 92 | data, 93 | }) 94 | } 95 | } 96 | 97 | 以上代码是扫块的IPC类,sendToClient是将处理好的消息发送给渲染进程,addListener将处理过的事件放到监听器 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/创建账户体系代码解析.md: -------------------------------------------------------------------------------- 1 | 2 | # 创建账户体系代码解析 3 | 4 | ### 1.创建账户呢称 5 | 6 | 前台发起创建呢陈请求,接受页面输入的用户名,接受后台返回来的UUID,并将其存储在localStorage中 7 | 8 | 63 | 64 | 65 | node服务接到前台的请求,校验前台输入的数据是否为空,不为空,产生UUID,并将UUID返回给前台界面 66 | 67 | 68 | import CreateAccountName from '../ipc/ipcCreateAccountNameMsg' 69 | import executeQuerySql from "../sqlite/sqlite"; 70 | const { shell } = require('electron'); 71 | var UUID = require('uuid'); 72 | 73 | const createAccountName = _createAccountNameIpc => ({ 74 | ['account-name'](event, accountName) { 75 | const requestBack = data => { 76 | _createAccountNameIpc.sendToClient('account-name-back', data) 77 | }; 78 | 79 | if(accountName != null) { 80 | console.log("get account name from front is " + accountName); 81 | requestBack({ 82 | success: true, 83 | succMsg:UUID.v1() 84 | }); 85 | }else { 86 | requestBack({ 87 | success: false, 88 | error:"Have you already input you name", 89 | errorCode:100 90 | }); 91 | } 92 | } 93 | }); 94 | 95 | export default createAccountName 96 | 97 | 前接受界面的密码之后,发起生成keystore的接口请求 98 | 99 | 181 | 182 | 接收前台数据后,校验keystore目录是否存在,存在就生keystore,并将keystore导入到文件,返回地址,私钥,keystore的json串和密文的私钥给前台 183 | 184 | import createAccountIpc from "../ipc/ipcCreateAccountMsg"; 185 | const {KEYSTOR_PATH} = require('../../constants/constants') 186 | 187 | const keythereum = require('keythereum'); 188 | const pwd = require('../base/secret'); 189 | const fs = require('fs'); 190 | 191 | const createAccount = _createAccountIpc => ({ 192 | ['generate-keystore'](event, passwd) { 193 | const requestBack = data => { 194 | _createAccountIpc.sendToClient('back-privateKey', data) 195 | }; 196 | if(passwd == null) { 197 | console.log("Receive password from front is null") 198 | requestBack({ 199 | success: false, 200 | error: "password is null", 201 | errorCode:201 202 | }) 203 | } else { 204 | if (!fs.existsSync(KEYSTOR_PATH)) { 205 | fs.mkdirSync(KEYSTOR_PATH); 206 | console.log("create keystore directry success and keystore directory is " + KEYSTOR_PATH); 207 | } 208 | var params = { keyBytes: 32, ivBytes: 16 }; 209 | var dk = keythereum.create(params); 210 | var options = { 211 | kdf: "pbkdf2", 212 | cipher: "aes-128-ctr", 213 | kdfparams: { 214 | c: 262144, 215 | dklen: 32, 216 | prf: "hmac-sha256" 217 | } 218 | }; 219 | var keyObject = keythereum.dump(passwd, dk.privateKey, dk.salt, dk.iv, options); 220 | if(keyObject) { 221 | console.log("keyObject address is ", keyObject.address); 222 | console.log("private key is ", dk.privateKey.toString('hex')) 223 | keythereum.exportToFile(keyObject, KEYSTOR_PATH); 224 | requestBack({ 225 | success: true, 226 | privateKey:{"address":keyObject.address, "privateKey":dk.privateKey.toString('hex'), "keystore":JSON.stringify(keyObject), "ciphertextPrivateKey":keyObject.crypto.ciphertext}, 227 | }) 228 | }else { 229 | requestBack({ 230 | success: false, 231 | error: "keyObject is null", 232 | errorCode:202 233 | }) 234 | } 235 | } 236 | } 237 | }); 238 | 239 | export default createAccount 240 | 241 | 前台发起账户体系的数据入库请求,入库的数据结构为{"account_id":this.account_id, "account_name":this.account_name, 242 | "account_passwd":this.account_passwd, "account_address":this.account_address, 243 | "keystore":this.keystore, "account_ciphertext_private_key":this.account_ciphertext_private_key} 244 | 245 | 351 | 352 | 后台接前端的数据入库请求,将数据存储到数据 353 | 354 | import createAccountIpc from "../ipc/ipcGenerateAccountMsg"; 355 | 356 | const { shell } = require('electron'); 357 | const keythereum = require('keythereum'); 358 | const pwd = require('../base/secret'); 359 | const dbInit = require('../sqlite/init'); 360 | 361 | const generateAccount = _generateAccountIpc => ({ 362 | ['generate-account'](event, accountJson) { 363 | const requestBack = data => { 364 | _generateAccountIpc.sendToClient('generate-account-back', data) 365 | }; 366 | 367 | if(accountJson == null) 368 | { 369 | console.log("get account information fron is null"); 370 | requestBack({ 371 | success: false, 372 | error:"accountJson is null", 373 | errorCode:300 374 | }) 375 | } else { 376 | console.log("account_id from fron is = ", accountJson.account_id); 377 | console.log("account_name from fron is = ", accountJson.account_name); 378 | console.log("account_password from fron is = ", accountJson.account_passwd); 379 | console.log("account_address from fron is = ", accountJson.account_address); 380 | console.log("keystore from front is = ", accountJson.keystore); 381 | console.log("corypt private key from front is " + accountJson.account_ciphertext_private_key); 382 | var db = dbInit.checkCreateLinkeyeDb(); 383 | if(!db){ 384 | console.log("db handle is null") 385 | requestBack({ 386 | success: false, 387 | error:"db handle is null", 388 | errorCode:301 389 | }) 390 | } else { 391 | var err = dbInit.createAccountTable("account", db); 392 | if(err == 'errone'){ 393 | console.log('create account table fail'); 394 | requestBack({ 395 | success: false, 396 | error: "create account table fail", 397 | errorCode:302 398 | }) 399 | }else { 400 | var insert = db.prepare("INSERT INTO account(account_id, account_name, account_passwd, account_address, account_keystore, account_ciphertext_private_key) VALUES (?, ?, ?, ?, ?, ?)"); 401 | insert.run(accountJson.account_id, accountJson.account_name, accountJson.account_passwd, accountJson.account_address, accountJson.keystore, accountJson.account_ciphertext_private_key); 402 | insert.finalize(); 403 | db.close(); 404 | requestBack({ 405 | success: true, 406 | generateMsg:"success", 407 | }) 408 | } 409 | } 410 | } 411 | } 412 | }); 413 | 414 | export default generateAccount 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/口令登陆和口令修改的源码解析.md: -------------------------------------------------------------------------------- 1 | # 口令登陆和口令修改的源码解析 2 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/导入私钥源码解析.md: -------------------------------------------------------------------------------- 1 | # 导入私钥源码解析 2 | 3 | 前端导入私钥请求代码,这个代码比较简单,这里不做深入的分析 4 | 5 | 118 | 119 | 后台接到前端的请求之后,先用私钥生成地址,然后用该地址,去查数据库,如果数据库中存在该用户,直接返回用户已经存在的错误码,如果用户不存在,则生成新的账户并将他存到数据库中 120 | 121 | import importPrivateKeyIpc from "../ipc/ipcImportKeyMsg"; 122 | const {KEYSTOR_PATH} = require('../../constants/constants') 123 | 124 | const keythereum = require('keythereum'); 125 | var UUID = require('uuid'); 126 | const dbInit = require('../sqlite/init'); 127 | const fs = require('fs'); 128 | 129 | function generateAccountNaem(randomFlag, min, max){ 130 | var str = "", range = min, arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 131 | if(randomFlag){ 132 | range = Math.round(Math.random() * (max-min)) + min; 133 | } 134 | for(var i=0; i ({ 142 | ['import-privateKey'](event, importKey) { 143 | const requestBack = data => { 144 | _importPrivateKeyIpc.sendToClient('import-privateKey-back', data) 145 | }; 146 | if(importKey == null) { 147 | console.log("Receive front import key is null") 148 | requestBack({ 149 | success: false, 150 | error:"db handle is null", 151 | errorCode:600 152 | }) 153 | } else { 154 | console.log("Receive front password success and password is " + importKey.password); 155 | console.log("Receive front private key success and private key is " + importKey.privateKey); 156 | console.log("Receive front private key path success and private key path is " + importKey.privateKeyPath); 157 | if (!fs.existsSync(KEYSTOR_PATH)) { 158 | fs.mkdirSync(KEYSTOR_PATH); 159 | console.log("create keystore directry success and keystore directory is " + KEYSTOR_PATH); 160 | } 161 | var privateKeyFront; 162 | if(!importKey.privateKeyPath){ 163 | privateKeyFront = importKey.privateKey; 164 | } else { 165 | privateKeyFront = fs.readFileSync(importKey.privateKeyPath, "utf-8"); 166 | console.log("read file content is " + privateKeyFront) 167 | } 168 | var accountId = UUID.v1(); 169 | var params = { keyBytes:32, ivBytes:16 }; 170 | var dk = keythereum.create(params); 171 | var options = { 172 | kdf: "pbkdf2", 173 | cipher: "aes-128-ctr", 174 | kdfparams: { 175 | c: 262144, 176 | dklen: 32, 177 | prf: "hmac-sha256" 178 | } 179 | }; 180 | var keyObject = keythereum.dump(importKey.password, privateKeyFront, dk.salt, dk.iv, options); 181 | if(keyObject) { 182 | console.log("keyObject address is ", keyObject.address); 183 | console.log("private key is ", dk.privateKey.toString('hex')) 184 | var db = dbInit.checkCreateLinkeyeDb(); 185 | if(!db){ 186 | console.log("db handle is null"); 187 | requestBack({ 188 | success: false, 189 | error:"db handle is null", 190 | errorCode:601 191 | }) 192 | } else { 193 | var err = dbInit.createAccountTable("account", db); 194 | if(err == 'errone'){ 195 | console.log('create account table fail'); 196 | requestBack({ 197 | success: false, 198 | error: "create account table fail", 199 | errorCode:602 200 | }) 201 | } else { 202 | var sql = "SELECT account_address FROM account where account_address = " + "\'" + keyObject.address + "\'"; 203 | db.all(sql, function w(err, row) { 204 | console.log("query database success and the length of row is " + row.length); 205 | if(row.length == 0) { 206 | var insert = db.prepare("INSERT INTO account(account_id, account_name, account_passwd, account_address, account_keystore, account_ciphertext_private_key) VALUES (?, ?, ?, ?, ?, ?)"); 207 | insert.run(accountId, generateAccountNaem(true, 7, 7), importKey.password, keyObject.address, JSON.stringify(keyObject), keyObject.crypto.ciphertext); 208 | insert.finalize(); 209 | db.close(); 210 | keythereum.exportToFile(keyObject, KEYSTOR_PATH); 211 | requestBack({ 212 | success: true, 213 | generateMsg: "success", 214 | }) 215 | } else { 216 | for(var i = 0; i < row.length; i++) { 217 | if(row[i].account_address == keyObject.address){ 218 | requestBack({ 219 | success:false, 220 | error:"account have already existed", 221 | errorCode:603, 222 | }) 223 | } 224 | } 225 | } 226 | }); 227 | } 228 | } 229 | }else{ 230 | requestBack({ 231 | success: false, 232 | error: "keyObject is null", 233 | errorCode:604 234 | }) 235 | } 236 | } 237 | } 238 | }); 239 | 240 | export default importPrivateKey 241 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/导出私钥源码解析.md: -------------------------------------------------------------------------------- 1 | 2 | # 导出私钥源码解析 3 | 4 | 导出私钥的代码,前端发起导出私钥请求,这里由于VUE无法调起系统弹出窗口并获取窗口目录路径的功能,所以这里分扯为两个接口调用,首先获取到用户的私钥,然后在将私钥存储到用户指定的目录。 5 | 6 | 82 | 83 | 84 | 导出私钥的后台接口,前台给定指定的地址和密码,接口中过地址获取到账户的keystore,然后取keystore中的密文的私钥,用密码解开私钥,如果密码错误的话,将不能成功地解析出私钥。 85 | 86 | import exportPrivateKeyIpc from "../ipc/ipcExportKeyMsg"; 87 | 88 | const dbInit = require('../sqlite/init'); 89 | const keythereum = require('keythereum'); 90 | 91 | const exportPrivateKey = _exportPrivateKeyIpc => ({ 92 | ['export-privateKey'](event, exportPivateKey) { 93 | const requestBack = data => { 94 | _exportPrivateKeyIpc.sendToClient('export-privateKey-back', data) 95 | }; 96 | if(exportPivateKey == null) { 97 | console.log("Receive front export private key is null") 98 | requestBack({ 99 | success:false, 100 | error:"get private key from front is null", 101 | errorCode:400 102 | }) 103 | } else { 104 | console.log("Receive address from front and the Address is " + exportPivateKey.address); 105 | console.log("Receive address from front and the Address is " + exportPivateKey.password); 106 | var db = dbInit.checkCreateLinkeyeDb(); 107 | if(!db){ 108 | console.log("db handle is null") 109 | requestBack({ 110 | success: false, 111 | error:"db handle is null", 112 | errorCode:401 113 | }) 114 | } else { 115 | console.log("start to operate database"); 116 | var sql = "SELECT account_keystore FROM account where account_address = " + "\'" + exportPivateKey.address + "\'"; 117 | db.each(sql, function w(err, row) { 118 | console.log("Query account_keystore success and account_private_key is" + row.account_keystore) 119 | var keyObj = JSON.parse(row.account_keystore); 120 | keythereum.recover(exportPivateKey.password, keyObj, function(privateKey){ 121 | console.log("recover private key is " + privateKey); 122 | if(Buffer.isBuffer(privateKey)){ 123 | requestBack({ 124 | success: true, 125 | privateKey:privateKey.toString('hex') 126 | }) 127 | }else { 128 | requestBack({ 129 | success:false, 130 | error:"password is wrong", 131 | errorCode:402 132 | }) 133 | } 134 | }); 135 | }); 136 | db.close(); 137 | } 138 | } 139 | } 140 | }); 141 | 142 | export default exportPrivateKey 143 | 144 | 将私钥存储到指定的目录 145 | 146 | import storePrivateKeyIpc from "../ipc/ipcStorePrivateKeyMsg"; 147 | const fs= require("fs"); 148 | 149 | const {dialog} = require('electron'); 150 | 151 | const storePrivateKey = _storePrivateKeyIpc => ({ 152 | ['store-privateKey'](event, storeKey) { 153 | const requestBack = data => { 154 | _storePrivateKeyIpc.sendToClient('store-privateKey-back', data) 155 | }; 156 | if(storeKey == null) { 157 | console.log("Receive storeKey from front success and store key is null") 158 | requestBack({ 159 | success:false, 160 | error:"param storeKey is null", 161 | errorCode:700, 162 | }) 163 | } else { 164 | console.log("Receive storeKey private key from front success and private key is " + storeKey.privateKey); 165 | var path = dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']}); 166 | var date = new Date(); 167 | var year = date.getFullYear(); 168 | var month = date.getMonth()+1; 169 | var day = date.getDate(); 170 | var hour = date.getHours(); 171 | var minute = date.getMinutes(); 172 | var second = date.getSeconds(); 173 | var dateTime = year+month+day+hour+minute+second; 174 | fs.writeFile(path + '/'+ dateTime +'privateKey.ert', storeKey.privateKey, {flag:'w',encoding:'utf-8',mode:'0666'}, function(err){ 175 | if(err){ 176 | console.log("write private key to file fail") 177 | requestBack({ 178 | success:false, 179 | error:"write private key to file fail", 180 | errorCode:701, 181 | }) 182 | }else{ 183 | console.log("write private key to file success"); 184 | requestBack({ 185 | success:true, 186 | writeMsg:"success", 187 | }) 188 | } 189 | }) 190 | } 191 | } 192 | }); 193 | 194 | export default storePrivateKey 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/获取账户余额源码解析.md: -------------------------------------------------------------------------------- 1 | # 获取账户余额源码解析 2 | 3 | 前端发起获取账户余额的请求,node先去数据库中获取账户列表返回给渲染进程,渲染进程接到用户列表之后,用用户的地址去linkeye公链上获取账户存在的余额,然后界面再去计算总的余额数。 4 | 5 | 6 | 152 | 153 | 154 | 以下是获取账户列表的node服务端接口 155 | 156 | import accountListIpc from '../ipc/ipcAccountList' 157 | const { shell } = require('electron'); 158 | const dbInit = require('../sqlite/init'); 159 | 160 | const accountList = _accountListIpc => ({ 161 | ['get-accounts'](event, accounts) { 162 | const requestBack = data => { 163 | _accountListIpc.sendToClient('get-accounts-back', data) 164 | }; 165 | if(accounts == null) 166 | { 167 | console.log("accounts is null"); 168 | requestBack({ 169 | success: false, 170 | error:"accounts is null", 171 | errorCode:903 172 | }); 173 | }else { 174 | var db = dbInit.checkCreateLinkeyeDb(); 175 | if(!db){ 176 | console.log("db handle is null"); 177 | requestBack({ 178 | success: false, 179 | error:"db handle is null", 180 | errorCode:904 181 | }); 182 | } 183 | var sql = "select account_id, account_name, account_address from account"; 184 | db.all(sql, function(err, res) { 185 | if(!err) { 186 | var accountLists = JSON.stringify(res); 187 | console.log("Get record from database is " + accountLists); 188 | requestBack({ 189 | success: true, 190 | accountList:accountLists 191 | }) 192 | } else { 193 | console.log("error occur, error is" + err); 194 | requestBack({ 195 | success: false, 196 | error:err, 197 | errorCode:905 198 | }); 199 | console.log(err); 200 | } 201 | }); 202 | } 203 | } 204 | }); 205 | 206 | export default accountList 207 | 208 | 209 | 去linkeye钱包节点上获取账户余额 210 | 211 | import getAccountBalanceIpc from '../ipc/ipcBalanceMsg' 212 | const {SEVER_IP, SEVER_PORT} = require('../../constants/constants') 213 | 214 | const https = require('https'); 215 | var Web3 = require("web3"); 216 | 217 | if (typeof web3 !== 'undefined') 218 | { 219 | var web3 = new Web3(web3.currentProvider); 220 | } 221 | else 222 | { 223 | var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 224 | } 225 | 226 | const getAccountBalance = _getAccountBalanceIpc => ({ 227 | ['balance'](event, accountAddress) { 228 | const requestBack = data => { 229 | _getAccountBalanceIpc.sendToClient('balance-back', data) 230 | }; 231 | 232 | if(!accountAddress) 233 | { 234 | console.log("get account address from front succusss accountAddress is null") 235 | requestBack({ 236 | success: false, 237 | error: "error occur,error is" + e, 238 | errorCode:1000 239 | }) 240 | }else { 241 | console.log("get account address from front succusss accountAddress is " + accountAddress) 242 | var body = { 243 | "jsonrpc": "2.0", 244 | "method": "let_getBalance", 245 | "params": ["0x" + accountAddress, "latest"], 246 | "id":83 247 | }; 248 | var bodyString = JSON.stringify(body); 249 | var headers = { 250 | 'Content-Type': 'application/json', 251 | 'Content-Length': bodyString.length 252 | }; 253 | //https://wallet.linkeye.com 254 | var options = { 255 | host: SEVER_IP, 256 | port: SEVER_PORT, 257 | path: '', 258 | method: 'POST', 259 | headers: headers 260 | }; 261 | var req = https.request(options, function (res) { 262 | res.setEncoding('utf-8'); 263 | var responseString = ''; 264 | res.on('data', function (data) { 265 | responseString += data; 266 | console.log("get balance from wallet node success and back data is " + data); 267 | var balance = JSON.parse(data); 268 | var tenBalance = parseInt(balance.result,16); 269 | console.log("balance from wallet is " + tenBalance); 270 | requestBack({ 271 | success: true, 272 | accountBalance:{"address":accountAddress, "balance":web3.fromWei(tenBalance, "ether")} 273 | }) 274 | }); 275 | res.on('end', function (res) { 276 | console.log("response end"); 277 | }); 278 | req.on('error', function (e) { 279 | console.log('error occur,error is', e); 280 | requestBack({ 281 | success: false, 282 | error: "error occur,error is" + e, 283 | errorCode:1001 284 | }) 285 | }); 286 | }); 287 | req.write(bodyString); 288 | req.end(); 289 | } 290 | } 291 | }); 292 | 293 | export default getAccountBalance 294 | 295 | 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/获取转账记录的源码解析.md: -------------------------------------------------------------------------------- 1 | # 获取转账记录的源码解析 2 | 3 | linkeye钱包暂时只支持获取自己转出的转账记录,后期将会加装入入账的记录,这部分其实就查本地库而已,渲染进程发起获取转账记录的请求,后台IPC监听到转账记录的请求之后,会去查询数据,返回以转账记录的列表。 4 | 5 | 以下是前端发起请求的代码 6 | 7 | 148 | 149 | 后台接口接收到请求之后去查询数据库,返回转账记录的列表 150 | 151 | import recordInfoIpc from '../ipc/ipcRecordMsg' 152 | const dbInit = require('../sqlite/init'); 153 | var schedule = require('node-schedule'); 154 | 155 | // var rule = new schedule.RecurrenceRule(); 156 | // var times = []; 157 | // for(var i = 1; i < 60; i += 4){ 158 | // times.push(i); 159 | // } 160 | // rule.second = times; 161 | 162 | const recordInfo = _recordInfoIpc => ({ 163 | ['record'](event, accountId) { 164 | // var j = schedule.scheduleJob(rule, function() { 165 | const requestBack = data => { 166 | _recordInfoIpc.sendToClient('record-back', data) 167 | }; 168 | var db = dbInit.checkCreateLinkeyeDb(); 169 | if(!db){ 170 | console.log("db handle is null"); 171 | requestBack({ 172 | success: false, 173 | error:"db handle is null", 174 | errorCode:909 175 | }) 176 | } else { 177 | console.log("get accountId from front success, accountId is " + accountId); 178 | var sql = "select * from record where account_id = " + "\'" + accountId + "\' order by strftime('%s', send_time) asc"; 179 | db.all(sql, function(err, res) { 180 | if(!err) { 181 | var result= JSON.stringify(res); 182 | console.log("Get record from database is " + result); 183 | requestBack({ 184 | success: true, 185 | recordInfo:result 186 | }) 187 | } else { 188 | console.log("error occur, error is" + err); 189 | requestBack({ 190 | success: false, 191 | error:err, 192 | errorCode:910 193 | }) 194 | } 195 | }); 196 | } 197 | // }) 198 | } 199 | }); 200 | 201 | export default recordInfo 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/资金密码修改的源码解析.md: -------------------------------------------------------------------------------- 1 | # 资金密码修改的源码解析 2 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/转账的源码解析.md: -------------------------------------------------------------------------------- 1 | # 转账的源码解析 2 | 3 | 转账可谓是钱包中最重要的一部分了,转账的整个业务流程为,客户端发起转账请求,服务端接收到请求信息之后,先去对交易进行签名,签名交易之后,再次发起转账申请,转账完成之后,应用程序需要去确认转账是否已经成功,linkeye的钱包程序是通过扫块来进行转账确认的,转账确认部分的源码解析请参照--转账确认部分的代码解析:https://github.com/guoshijiang/go-ethereum-code-analysis/blob/master/wallet/linkeye-wallet/%E8%BD%AC%E8%B4%A6%E7%A1%AE%E8%AE%A4%E5%8C%BA%E5%9D%97%E6%89%AB%E6%8F%8F%E4%BB%A3%E7%A0%81%E8%A7%A3%E6%9E%90.md 4 | 5 | 渲染进程发起转账请求 6 | 7 | 8 | 245 | 246 | 247 | node服务接收到转账请求之后,会先去linkeye的公链上获取交易的nonce,nonce是链上唯一标识交易的ID,获取完nonce之后,再去获取链上的区块数量以便于确认后面的转账是否能转成功。发起转账之前,还需要对交易进行签名。 248 | 249 | 转账的代码 250 | 251 | import sendTransactionIpc from '../ipc/ipcSendMsg' 252 | const {SEVER_IP, SEVER_PORT} = require('../../constants/constants') 253 | 254 | const dba = require('../sqlite/sqlite'); 255 | const dbInit = require('../sqlite/init'); 256 | const https=require('https'); 257 | const send = require('../base/sendTranaction'); 258 | const pwd = require('../base/secret'); 259 | const sendRecord = require('./sendRecord'); 260 | const keythereum = require('keythereum'); 261 | 262 | const sendTransaction = _sendTransactionIpc => ({ 263 | ['send'](event, sendInfo) { 264 | const requestBack = data => { 265 | _sendTransactionIpc.sendToClient('send-back', data) 266 | } 267 | if(sendInfo == null) 268 | { 269 | console.log("Receive front send information error, param sendInfo is null"); 270 | requestBack({ 271 | success:false, 272 | error:"params is null", 273 | errorCode:2000 274 | }) 275 | } else { 276 | console.log("get account_id from front is + " + sendInfo.accountId) 277 | console.log("get password from front is " + sendInfo.password); 278 | console.log("get from address from front is " + sendInfo.fromAddress); 279 | console.log("get toAddress from front is " + sendInfo.toAddress); 280 | console.log("get send balance from front is " + sendInfo.sendToBalance); 281 | console.log("get fee from front is " + sendInfo.sendFee); 282 | console.log("get send content from front is " + sendInfo.sendContent); 283 | var db = dbInit.checkCreateLinkeyeDb(); 284 | if(!db){ 285 | requestBack({ 286 | success:false, 287 | error:"database handle is null", 288 | errorCode:2001 289 | }); 290 | } else { 291 | var sql = "SELECT account_passwd, account_keystore FROM account where account_address = " + "\'" + sendInfo.fromAddress + "\'"; 292 | db.each(sql, function w(err, row) { 293 | console.log("Query account_password from database, the account_password is " + row.account_passwd); 294 | console.log("Query account_keystore from database, the account_keystore is " + row.account_keystore); 295 | if (row.account_passwd == null || row.account_keystore == null) { 296 | console.log("account password and account keystore is null"); 297 | requestBack({ 298 | success:false, 299 | error:"one of param is null", 300 | errorCode:2002 301 | }) 302 | } else { 303 | console.log("account password is " + row.account_passwd); 304 | if(row.account_passwd == sendInfo.password) { 305 | console.log("two password is equal"); 306 | var keyObj = JSON.parse(row.account_keystore); 307 | keythereum.recover(sendInfo.password, keyObj, function (privateKey) { 308 | var body = { 309 | "jsonrpc":"2.0", 310 | "method":"let_getTransactionCount", 311 | "params":['0x'+ sendInfo.fromAddress, "latest"], 312 | "id":1 313 | }; 314 | var bodyString = JSON.stringify(body); 315 | var headers = { 316 | 'Content-Type':'application/json', 317 | 'Content-Length':bodyString.length 318 | }; 319 | var options = { 320 | host:SEVER_IP, 321 | port:SEVER_PORT, 322 | path:'', 323 | method:'POST', 324 | headers:headers 325 | }; 326 | 327 | var req = https.request(options, function (res) { 328 | res.setEncoding('utf-8'); 329 | var responseString = ''; 330 | res.on('data', function (data) { 331 | responseString += data; 332 | // nonce from wallet node 333 | console.log("get nonce from wallet node json is " + data); 334 | var nonceStr = JSON.parse(data); 335 | console.log("nonce from wallet is " + nonceStr.result); 336 | var signTx = send.getSignTransaction(privateKey, nonceStr.result, sendInfo.toAddress, sendInfo.sendToBalance, sendInfo.sendFee, sendInfo.sendContent); 337 | if( signTx == null ) { 338 | requestBack({ 339 | success:false, 340 | error:"sign transaction result is null", 341 | errorCode:2003 342 | }) 343 | } 344 | console.log("The sign of the transaction is " + signTx); 345 | 346 | 347 | var body = { 348 | "jsonrpc": "2.0", 349 | "method": "let_blockNumber", 350 | "params":[], 351 | "id":83 352 | }; 353 | 354 | var bodyString = JSON.stringify(body); 355 | var headers = { 356 | 'Content-Type':'application/json', 357 | 'Content-Length':bodyString.length 358 | }; 359 | 360 | var options = { 361 | host:SEVER_IP, 362 | port:SEVER_PORT, 363 | path:'', 364 | method:'POST', 365 | headers:headers 366 | }; 367 | 368 | var req = https.request(options, function (res) { 369 | res.setEncoding('utf-8'); 370 | var responseString = ''; 371 | res.on('data', function (data) { 372 | responseString += data; 373 | console.log("get block number from wallet node success and back data is " + data) 374 | var blockNumberStr = JSON.parse(data); 375 | var blockNumber = parseInt(blockNumberStr.result,16); 376 | console.log("block number from wallet is " + blockNumber); 377 | var body = { 378 | "jsonrpc":"2.0", 379 | "method":"let_sendRawTransaction", 380 | "params":[signTx], 381 | "id":1 382 | } 383 | var bodyString = JSON.stringify(body); 384 | var headers = { 385 | 'Content-Type':'application/json', 386 | 'Content-Length':bodyString.length 387 | }; 388 | var options = { 389 | host:SEVER_IP, 390 | port:SEVER_PORT, 391 | path:'', 392 | method:'POST', 393 | headers:headers 394 | }; 395 | var req = https.request(options, function (res) { 396 | res.setEncoding('utf-8'); 397 | var responseString = ''; 398 | res.on('data', function (data) { 399 | responseString += data; 400 | console.log("send information wallet back data is= " + data) 401 | var sendInformation = JSON.parse(data); 402 | if(sendInformation.result != null ) { 403 | console.log("send data from wallet parse is " + sendInformation.result); 404 | sendRecord.sendRcordData(sendInfo.fromAddress, sendInfo.accountId, sendInfo.toAddress, 405 | sendInfo.sendToBalance, sendInfo.sendFee, sendInfo.sendContent, "1", blockNumber, sendInformation.result); 406 | requestBack({ 407 | success:true, 408 | succHash:"success", 409 | }) 410 | } else { 411 | sendRecord.sendRcordData(sendInfo.fromAddress, sendInfo.accountId, sendInfo.toAddress, 412 | sendInfo.sendToBalance, sendInfo.sendFee, sendInfo.sendContent, "2", blockNumber, "transaction fail, there isn't hash in database"); 413 | requestBack({ 414 | success:false, 415 | error:"fail", 416 | errorCode:2004 417 | }) 418 | } 419 | }); 420 | res.on('end', function (res) { 421 | console.log("response end"); 422 | }); 423 | req.on('error', function (e) { 424 | console.log('error occur,error is', e); 425 | requestBack({ 426 | success:false, 427 | error:"error occur,error is" + e, 428 | errorCode:2005 429 | }) 430 | }); 431 | }); 432 | req.write(bodyString); 433 | req.end(); 434 | }); 435 | res.on('end', function (res) { 436 | console.log("response end"); 437 | }); 438 | 439 | req.on('error', function (e) { 440 | console.log('error occur,error is', e); 441 | requestBack({ 442 | success: false, 443 | error:'error occur,error is' + e, 444 | errorCode:2006 445 | }) 446 | }); 447 | }); 448 | req.write(bodyString); 449 | req.end(); 450 | }); 451 | res.on('end', function (res) { 452 | console.log("response end"); 453 | }); 454 | req.on('error', function (e) { 455 | console.log('error occur,error is', e); 456 | requestBack({ 457 | success:false, 458 | error:"error occur,error is" + e, 459 | errorCode:2007 460 | }) 461 | }); 462 | }); 463 | req.write(bodyString); 464 | req.end(); 465 | }); 466 | } else { 467 | requestBack({ 468 | success:false, 469 | error:"password is wrong", 470 | errorCode:2008, 471 | }) 472 | } 473 | } 474 | }); 475 | } 476 | } 477 | } 478 | }); 479 | 480 | export default sendTransaction 481 | 482 | 签名的代码 483 | 484 | const transaction = require('ethereumjs-tx'); 485 | const dbInit = require('../sqlite/init'); 486 | var Web3 = require("web3"); 487 | 488 | if (typeof web3 !== 'undefined') 489 | { 490 | var web3 = new Web3(web3.currentProvider); 491 | } 492 | else 493 | { 494 | var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 495 | } 496 | 497 | /* 498 | * method:sendSignTransaction send a transaction to our linkeye wallet node 499 | * params:a json string 500 | * return:success return transaction hash 501 | */ 502 | function getSignTransaction(privateKey, nonce, toAddress, sendToBalance, sendFee, sendContent) { 503 | if(!privateKey || !nonce || !toAddress || !sendToBalance || !sendFee) { 504 | console.log("one of fromAddress, toAddress, sendToBalance, sendFee is null, please give a valid param"); 505 | } else { 506 | console.log("param is valid, start sign transaction"); 507 | var numBalance = parseFloat(sendToBalance); 508 | var balancetoWei = web3.toWei(numBalance, "ether"); 509 | console.log("balancetoWei is " + balancetoWei); 510 | var oxNumBalance = parseInt(balancetoWei).toString(16); 511 | console.log("16 oxNumBalance is " + oxNumBalance); 512 | var gasFee = parseFloat(sendFee); 513 | var gasFeeToWei = web3.toWei(gasFee, "ether"); 514 | console.log("gas fee to wei is " + gasFeeToWei); 515 | var gas = gasFeeToWei / 100000000000; 516 | console.log("param gas is " + gas); 517 | var oxgasFeeToWei = parseInt(gas).toString(16); 518 | console.log("16 oxgasFeeToWei is " + oxgasFeeToWei); 519 | var privateKeyBuffer = privateKey; 520 | var letAddress = toAddress.substr(3); //23 79 0a 25 6a 41 c3 df a1 92 57 97 ae 47 6e 36 65 15 8f 76 521 | console.log("let address is " + letAddress); 522 | var rawTx = { 523 | nonce:nonce, 524 | gasPrice: '0x174876e800', 525 | gas:'0x'+ oxgasFeeToWei, 526 | to: '0x' + letAddress, 527 | value:'0x' + oxNumBalance, 528 | subId:'0x00000000000000000000000000000000', 529 | data:'0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', 530 | }; 531 | var tx = new transaction(rawTx); 532 | tx.sign(privateKeyBuffer); 533 | var serializedTx = tx.serialize(); 534 | if(serializedTx == null) { 535 | console.log("Serialized transaction fail") 536 | } else { 537 | console.log("Serialized transaction success and the result is " + serializedTx.toString('hex')); 538 | console.log("The sender address is " + tx.getSenderAddress().toString('hex')); 539 | if (tx.verifySignature()) { 540 | console.log('Signature Checks out!') 541 | } else { 542 | console.log("Signature checks fail") 543 | } 544 | } 545 | } 546 | return '0x' + serializedTx.toString('hex') 547 | } 548 | 549 | exports.getSignTransaction = getSignTransaction; 550 | 551 | 552 | 553 | 554 | -------------------------------------------------------------------------------- /wallet/linkeye-wallet/转账确认区块扫描代码解析.md: -------------------------------------------------------------------------------- 1 | 2 | # 转账确认区块扫描代码解析 3 | 4 | 前台检查网络是否链接之后发去扫块请求 5 | 6 | 38 | 39 | 40 | nodejs端接到请求之后,先去查看数据库中转账在确认中的转账记录,超过三个区块就认为转账成功,linkeye公链设计的就一笔转账发起后,超过三个区块,就认为转账成功,linkeye公链的共识算法采用Dpos加pbft,pbft确认之后链就不会发生分叉。 41 | 42 | 43 | import queryBlockIpc from '../ipc/ipcQueryBlockMsg' 44 | const {SEVER_IP, SEVER_PORT} = require('../../constants/constants') 45 | const https = require('https'); 46 | const dbInit = require('../sqlite/init'); 47 | 48 | const queryBlock = _queryBlockIpc => ({ 49 | ['block'](event, blockInfo) { 50 | const requestBack = data => { 51 | _queryBlockIpc.sendToClient('block-back', data) 52 | }; 53 | if(blockInfo != null) { 54 | var db = dbInit.checkCreateLinkeyeDb(); 55 | if(!db){ 56 | console.log("db handle is null") 57 | return ; 58 | } else { 59 | var errThree = dbInit.createSendTable("record", db); 60 | if(errThree == "errtwo"){ 61 | console.log('create record table fail'); 62 | return ; 63 | } 64 | } 65 | var body = { 66 | "jsonrpc": "2.0", 67 | "method": "let_blockNumber", 68 | "params":[], 69 | "id":83 70 | }; 71 | var bodyString = JSON.stringify(body); 72 | var headers = { 73 | 'Content-Type':'application/json', 74 | 'Content-Length':bodyString.length 75 | }; 76 | var options = { 77 | host:SEVER_IP, 78 | port:SEVER_PORT, 79 | path:'', 80 | method:'POST', 81 | headers:headers 82 | }; 83 | var req = https.request(options, function (res) { 84 | res.setEncoding('utf-8'); 85 | var responseString = ''; 86 | res.on('data', function (data) { 87 | responseString += data; 88 | console.log("get block number from wallet node success and back data is " + data) 89 | var blockNumberStr = JSON.parse(data); 90 | var blockNumber = parseInt(blockNumberStr.result, 16); 91 | var db = dbInit.checkCreateLinkeyeDb(); 92 | if(!db){ 93 | console.log("db handle is null") 94 | } else { 95 | console.log("start to operate database") 96 | var sql = "SELECT send_id, account_id, send_time, account_addr_from, account_addr_to, send_balance, service_charge, comment, send_status, block_number " + 97 | "FROM record where send_status = '1'"; 98 | db.all(sql, function w(err, row) { 99 | console.log("Query record database success and the data is" + JSON.stringify(row)) 100 | var recordInfo = JSON.stringify(row); 101 | for(var i = 0; i < row.length; i++) { 102 | if(blockNumber - parseInt(row[i].block_number) > 3) { 103 | var modify=db.prepare("UPDATE record set send_status=?, block_number=? where send_id = ?") 104 | modify.run("0", row[i].block_number ,row[i].send_id) 105 | modify.finalize(); 106 | } 107 | } 108 | }); 109 | db.close(); 110 | } 111 | console.log("block number from wallet is " + blockNumber); 112 | }); 113 | res.on('end', function (res) { 114 | console.log("response end"); 115 | }); 116 | req.on('error', function (e) { 117 | console.log('error occur,error is', e); 118 | }); 119 | }); 120 | req.write(bodyString); 121 | req.end(); 122 | } 123 | } 124 | }); 125 | 126 | export default queryBlock 127 | 128 | 129 | IPC类 130 | 131 | import queryBlock from '../block/queryBlock' 132 | 133 | const {CLIENT_NORMAL_MSG, CRAWLER_NORMAL_MSG,} = require('../../constants/constants') 134 | 135 | export default class queryBlockIpc { 136 | constructor(listener, sender) { 137 | this.listener = listener 138 | this.sender = sender 139 | this.addListener(CLIENT_NORMAL_MSG, this.handleFn.bind(this)) 140 | this.handlerQueryBlockIpc = queryBlock(this) 141 | } 142 | 143 | handleFn(event, data) { 144 | try { 145 | this.handlerQueryBlockIpc[data.type](event, data.data) 146 | } catch (error) { 147 | console.error('handler event error:' + error.message) 148 | } 149 | } 150 | 151 | addListener(chanel, cb) { 152 | this.listener.on(chanel, cb) 153 | } 154 | 155 | _sendMsg(chanel, msgBody) { 156 | this.sender.send(chanel, msgBody) 157 | } 158 | 159 | sendToClient(type, data) { 160 | this._sendMsg(CRAWLER_NORMAL_MSG, { 161 | type, 162 | data, 163 | }) 164 | } 165 | } 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /wallet/material/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 使用nodejs开发一个简易版的钱包的流程 3 | 4 | ### 1.生成助记词 5 | 6 | 使用bip39库,关于bip系列库的讲解,将在以后的文章中做详细说明 7 | 8 | const bip39 = require('bip39'); 9 | 10 | 钱包的助记词一般为12个单词,下面代码生成的助记词就是12个 11 | 12 | let words = bip39.generateMnemonic(); 13 | 14 | 接下来,再根据助记词和密码生成随机数种植 15 | 16 | let password = '123456'; 17 | let seedAsHex = bip39.mnemonicToSeedHex(words, password); 18 | console.log("--------------random deed------------------"); 19 | console.log(seedAsHex); 20 | 21 | ### 2.生成keystore,导出私钥 22 | 23 | 这里需要用到keythereum库,关于keythereum库咱们会有专门的文章介绍 24 | 25 | var params = { keyBytes: 32, ivBytes: 16 }; 26 | var dk = keythereum.create(params); 27 | var options = { 28 | kdf: "pbkdf2", 29 | cipher: "aes-128-ctr", 30 | kdfparams: { 31 | c: 262144, 32 | dklen: 32, 33 | prf: "hmac-sha256" 34 | } 35 | }; 36 | var keyObject = keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options); 37 | keythereum.exportToFile(keyObject); 38 | 39 | 这里生成keystore如果想用随机数种子,那么把随机数种子变成私钥就行,导出的私钥其实就是将dk.privateKey导出给用户就行 40 | 41 | ### 3.使用web3和以太坊交互 42 | 43 | 假设咱们开发的是一个以太坊轻钱包,那么咱们得和以太坊的钱包节点进行交互,因此我需要使用到web3js。 44 | 45 | 引入web3js 46 | 47 | var Web3 = require("web3"); 48 | 49 | if (typeof web3 !== 'undefined') 50 | { 51 | web3 = new Web3(web3.currentProvider); 52 | } 53 | else 54 | { 55 | web3 = new Web3(new Web3.providers.HttpProvider("http://10.23.1.209:8545")); 56 | } 57 | 58 | ### 4.获取账户余额 59 | 60 | web3.eth.getBalance("0x68db18a9cd87403c39e84467b332195b43fc33b5", function(err, result) 61 | { 62 | if (err == null) 63 | { 64 | console.log('~balance Ether:' +web3.fromWei(result, "ether")); 65 | } 66 | else 67 | { 68 | console.log('~balance Ether:' + web3.fromWei(result, "ether")); 69 | } 70 | }); 71 | 72 | ### 5.获取交易的nonce 73 | 74 | web3.eth.getTransactionCount("0x68db18a9cd87403c39e84467b332195b43fc33b5", function (err, result) 75 | { 76 | if (err == null) 77 | { 78 | console.log('nonce:' + result); 79 | } 80 | else 81 | { 82 | console.log('nonce:' + result); 83 | } 84 | }); 85 | 86 | ### 6.发起转账 87 | 88 | 转账需要使用到ethereumjs-tx库,对交易进行签名,关于ethereumjs-tx库,请参阅咱们相关的文章 89 | 90 | var Tx = require('ethereumjs-tx'); 91 | var privateKey = new Buffer.from('0f8e7b1b99f49d1d94ac42084216a95fe5967caec6bba35c62c911f6c4eafa95', 'hex') 92 | 93 | var rawTx = { 94 | subId:'0x0000000000000000000000000000000000', 95 | nonce: '0x1', 96 | gasPrice: '0x1a13b8600', 97 | gas: '0xf4240', 98 | to: '0x82aeb528664bb153d2114ae7ca4ef118ef1e7a98', 99 | value: '0x8ac7230489e80000', 100 | data:'0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', 101 | //chainId:"10" 102 | }; 103 | 104 | var tx = new Tx(rawTx); 105 | tx.sign(privateKey); 106 | 107 | var serializedTx = tx.serialize(); 108 | 109 | if (tx.verifySignature()) 110 | { 111 | console.log('Signature Checks out!') 112 | } 113 | 114 | web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) { 115 | if (!err) 116 | { 117 | console.log("hash:" + hash); // "0x7f9fade1c0d57a7af66ab4ead79fade1c0d57a7af66ab4ead7c2c2eb7b11a91385" 118 | } 119 | else 120 | { 121 | console.log(err); 122 | } 123 | }); 124 | -------------------------------------------------------------------------------- /wallet/material/lib/bip系列.md: -------------------------------------------------------------------------------- 1 | # BIP系列 2 | 3 | ## 1.bip21 4 | 5 | bip21是一个兼容性的url编码库 6 | 7 | ### 1.1.使用案例 8 | 9 | 请在node环境下使用 10 | 11 | 引入依赖库 bip21 12 | 13 | var bip21 = require('bip21') 14 | 解码 15 | 16 | bip21.decode('bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar') 17 | 18 | 编码 19 | 20 | bip21.encode('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') 21 | bip21.encode('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', {amount: 20.3, label: 'Foobar'}) 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /wallet/material/lib/ethereumjs-tx.md: -------------------------------------------------------------------------------- 1 | 2 | # ethereumjs-tx库介绍 3 | 4 | 这个库主要是用来签名交易的,签名完的交易通过web3发送到以太坊钱包节点;此库运行的环境ECMAScript 6(ES6)为最低环境。 在缺乏ES6支持的浏览器中,请使用垫片(如es6-shim),否则该库可能运行不起来。 5 | 6 | ## 1.安装 7 | 8 | npm install ethereumjs-tx 9 | 10 | ## 2.使用案例 11 | 12 | const EthereumTx = require('ethereumjs-tx') 13 | const privateKey = Buffer.from('e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex') 14 | 15 | const txParams = { 16 | nonce: '0x00', 17 | gasPrice: '0x09184e72a000', 18 | gasLimit: '0x2710', 19 | to: '0x0000000000000000000000000000000000000000', 20 | value: '0x00', 21 | data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', 22 | // EIP 155 chainId - mainnet: 1, ropsten: 3 23 | chainId: 3 24 | } 25 | 26 | const tx = new EthereumTx(txParams) 27 | tx.sign(privateKey) 28 | const serializedTx = tx.serialize() 29 | -------------------------------------------------------------------------------- /wallet/material/lib/keythereum.md: -------------------------------------------------------------------------------- 1 | # Keythereum库介绍 2 | 3 | Keythereum是一个生成,导入和导出以太坊keys的一个javascript工具。这个库提供了一中在本地或者web钱包中使用相同的账户。它可以用于可验证的冷库钱包。 4 | 5 | Keythereum使用相同的密钥派生函数(PBKDF2-SHA256或scrypt),对称密码(AES-128-CTR或AES-128-CBC)和消息验证代码作为geth。 您可以将生成的密钥导出到文件,将其复制到数据目录的密钥库中,并立即在您的本地Ethereum客户端中使用它。 6 | 7 | 注意:从版本0.5.0开始,keythereum的加密和解密函数都会返回缓冲区而不是字符串。 对于任何直接使用这些功能的人来说,这是一个突破性改变! 8 | 9 | ## 1.安装 10 | 11 | npm install keythereum 12 | 13 | 当然运行上面的命令,你需要安装nodejs,关于nodejs的安装,你可以选择自己喜欢的方式去进行安装 14 | 15 | ## 2.使用 16 | 17 | 在nodejs中使用keythereum,你需要引入它,使用关键字require 18 | 19 | var keythereum = require("keythereum"); 20 | 21 | 压缩文件dist/keythereum.min.js在浏览器中使用时。 包只需将keythereum对象附加到窗口即可: 22 | 23 | 24 | 25 | ### 2.1.产生key 26 | 27 | 生成一个新的随机私钥(256位),以及密钥派生函数使用的salt(256位),以及用于AES-128-CTR加密密钥的初始化向量(128位)。 如果传递一个回调函数,create是异步的,否则是同步的。 28 | 29 | var params = { keyBytes: 32, ivBytes: 16 }; 30 | 31 | //synchronous 32 | var dk = keythereum.create(params); 33 | // dk: 34 | { 35 | privateKey: , 36 | iv: , 37 | salt: 38 | } 39 | 40 | //asynchronous 41 | keythereum.create(params, function (dk) { 42 | // do stuff! 43 | }); 44 | 45 | ### 2.2.导出key 46 | 47 | 您需要指定密码和(可选)密钥派生函数。 如果未指定,则使用PBKDF2-SHA256来派生AES密钥。 48 | 49 | var password = "wheethereum"; 50 | var kdf = "pbkdf2"; 51 | 52 | 转储功能用于将密钥信息导出到密钥存储“秘密存储”格式。 如果提供回调函数作为要转储的第六个参数,则它将异步运行: 53 | 54 | // Note: if options is unspecified, the values in keythereum.constants are used. 55 | var options = { 56 | kdf: "pbkdf2", 57 | cipher: "aes-128-ctr", 58 | kdfparams: { 59 | c: 262144, 60 | dklen: 32, 61 | prf: "hmac-sha256" 62 | } 63 | }; 64 | 65 | // synchronous 66 | var keyObject = keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options); 67 | // keyObject: 68 | { 69 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 70 | Crypto: { 71 | cipher: "aes-128-ctr", 72 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 73 | cipherparams: { 74 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 75 | }, 76 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", 77 | kdf: "pbkdf2", 78 | kdfparams: { 79 | c: 262144, 80 | dklen: 32, 81 | prf: "hmac-sha256", 82 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 83 | } 84 | }, 85 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 86 | version: 3 87 | } 88 | 89 | // asynchronous 90 | keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) { 91 | // do stuff! 92 | }); 93 | 94 | dump创建一个对象而不是JSON字符串。 在Node中,exportToFile方法提供了一种将此格式化的密钥对象导出到文件的简单方法。 它在密钥库子目录中创建一个JSON文件,并使用geth的当前文件命名约定(ISO时间戳与密钥的派生Ethereum地址连接)。 95 | 96 | keythereum.exportToFile(keyObject); 97 | 98 | 成功导出密钥后,您将看到如下消息: 99 | 100 | Saved to file: 101 | keystore/UTC--2015-08-11T06:13:53.359Z--008aeeda4d805471df9b2a5b0f38a0c3bcba786b 102 | 103 | To use with geth, copy this file to your Ethereum keystore folder 104 | (usually ~/.ethereum/keystore). 105 | 106 | 107 | ### 2.3.导出key 108 | 109 | 从geth的keystore导入密钥只能在Node上完成。 JSON文件被解析成与上面的keyObject具有相同结构的对象。 110 | 111 | // Specify a data directory (optional; defaults to ~/.ethereum) 112 | var datadir = "/home/jack/.ethereum-test"; 113 | 114 | // Synchronous 115 | var keyObject = keythereum.importFromFile(address, datadir); 116 | 117 | // Asynchronous 118 | keythereum.importFromFile(address, datadir, function (keyObject) { 119 | // do stuff 120 | }); 121 | 122 | 这已经过版本3和版本1的测试,但没有版本2的密钥。 (如果有的话,请给我一个版本2的keystore文件,这样我就可以测试它!) 123 | 124 | // synchronous 125 | var privateKey = keythereum.recover(password, keyObject); 126 | // privateKey: 127 | 128 | 129 | // Asynchronous 130 | keythereum.recover(password, keyObject, function (privateKey) { 131 | // do stuff 132 | }); 133 | 134 | 135 | -------------------------------------------------------------------------------- /wallet/material/lib/web3js.md: -------------------------------------------------------------------------------- 1 | 2 | # web3js 3 | 4 | ## 1.web3js简介 5 | 6 | web3js的以太坊提供的与节点进行通行的JavaScript库,这是Ethereum兼容的JavaScript API,它实现了通用JSON RPC规范。 它可以作为npm作为节点模块,作为可嵌入的js和作为meteor.js包使用。web3中有eth对象-web3.eth 具体来表示与以太坊区块链之间的交互。shh对象 - web3.shh表示Whisper协议的相关交互。后续我们会继续介绍其它一些web3协议中的对象。web3提供了HttpProvider和IpcProvider两中通信方式。 7 | 8 | ## 2.web3的安装 9 | 10 | 1.npm方式安装 11 | 12 | npm install web3 13 | 14 | 2.bower方式安装 15 | 16 | bower install web3 17 | 18 | 3.将web3添加到meteor中 19 | 20 | meteor add ethereum:web3 21 | 4.vanilla 22 | 23 | dist./web3.min.js 24 | 25 | 5.yarn方式管理web3 26 | 27 | yarn add web3 28 | 29 | 6.cdn 30 | 31 | 32 | 33 | 7.Component 34 | 35 | component install ethereum/web3.js 36 | 37 | 你可以根据自己的环境和自身的喜好通过以上方式来安装web3,当然如果你还有其他的方式安装,你也可以告诉我们 38 | 39 | ## 3.web3的使用 40 | 41 | ### 3.1.定义web3 42 | 43 | 1.直接通过全局命名空间使用web3对象 44 | 45 | console.log(web3); // {eth: .., shh: ...} // it's here! 46 | 47 | 2.设置一个provider(如HttpProvider) 48 | 49 | if (typeof web3 !== 'undefined') { 50 | web3 = new Web3(web3.currentProvider); 51 | } else { 52 | web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 53 | } 54 | 55 | 3.设置一个provider(HttpProvider使用http的基本认证) 56 | 57 | web3.setProvider(new web3.providers.HttpProvider('http://host.url', 0, BasicAuthUsername, BasicAuthPassword)); 58 | 59 | 60 | ### 3.2.使用web3的案例 61 | 62 | 1.获取web3的版本 63 | 64 | var version = web3.version.api; 65 | console.log(version); 66 | 67 | 2.从以太坊节点上获取用户数 68 | 69 | var accounts = web3.eth.accounts; 70 | console.log(accounts); 71 | 72 | 3.从以太坊节点上获取区块数量 73 | 74 | var bnum = web3.eth.blockNumber; 75 | console.log(bnum); 76 | 77 | 4.获取0xfa319c8ea9b00513bb1a112de2073263aa92c930账户余额 78 | 79 | web3.eth.getBalance("0xfa319c8ea9b00513bb1a112de2073263aa 80 | 92c930", function(err, result) 81 | { 82 | if (err == null) 83 | { 84 | console.log('~balance:' + result); 85 | } 86 | else 87 | { 88 | console.log('~balance:' + result); 89 | } 90 | }); 91 | 92 | 5.获取交易的结果 93 | 94 | web3.eth.getTransaction('0x043e9bfaea7854f41e0d55121788 95 | 51be1d2d584bd5ee339ae03cac1796b04781', function(err, 96 | result) 97 | { 98 | if (err == null) 99 | { 100 | console.log('~transaction:' + result); 101 | } 102 | else 103 | { 104 | console.log('err', + err); 105 | } 106 | }); 107 | 108 | 6.实现转账代码 109 | 要发起转账,需要安装一个nodeJs的模块,这个模块叫做ethereumjs-tx 110 | 代码实现如下: 111 | 112 | var Tx = require('ethereumjs-tx'); 113 | var privateKey = new Buffer('7418BC72BAB71BDEB450D4AD91BA 114 | 07C03E50B21D13B7C74E806510EAE9769475', 'hex'); 115 | var rawTx = 116 | { 117 | nonce:'', 118 | gasPrice:'0x3b9aca00', 119 | gasLimit:'0x493e0', 120 | to: '0x545a05d023d746e342ab8d31e792b4a2f6e9e19e', 121 | value: '10', 122 | data: ' ' 123 | }; 124 | 125 | var tx = new Tx(rawTx); 126 | tx.sign(privateKey); 127 | 128 | var serializedTx = tx.serialize(); 129 | web3.eth.sendRawTransaction('0x' + serializedTx. 130 | toString('hex'), function (err, hash) 131 | { 132 | console.log('交易结果:' + hash); 133 | 134 | if (callback && typeof(callback) === "function") 135 | { 136 | if(!err) 137 | { 138 | callback(null, hash); 139 | } 140 | else 141 | { 142 | callback(err, null); 143 | } 144 | } 145 | }); 146 | -------------------------------------------------------------------------------- /wallet/material/lib/开发钱包相关的库简介.md: -------------------------------------------------------------------------------- 1 | # 以太坊钱包 2 | 3 | ## 区块链钱包分类 4 | 5 | * on-chain:给一个钱包地址发送数字货币, 这笔交易在全网广播、被确认、被打包进区块。这是发生在链上的,被称为on-chain交易。on-chain钱包需要自己保管私钥。 6 | * off-chain:相对于on-chain交易是off-chain交易。通常,通过交易所进行的交易是off-chain的,本人并没有私钥。私钥在交易所,由交易所托管。所以交易所的钱包也是中心化的钱包。 7 | * 冷钱包:冷即离线、断网,也就是说私钥存储的位置不能被网络所访问。例如纸钱包、脑钱包、硬件钱包等等。 8 | * 热钱包:热即联网,也就是私钥存储在能被网络访问的位置。例如存放在交易所的、在线钱包网站、手机App钱包都属于热钱包。通常而言,冷钱包更加安全,热钱包使用更加方便。 9 | * 全节点钱包:全节点钱包通俗的来说就是同步了全部的以太坊区块信息的钱包 10 | * 轻钱包:钱包和节点分离,不需要同步以太坊区块的信息,它不必保存所有区块的数据,只保存跟自己相关的数据。基本可以实现去中心化。 11 | * 移动端钱包:app钱包 12 | * 中心化钱包:在交易所中的钱包,以及类似 OKLink 提供的保险柜服务。 13 | 14 | ## 以太坊现有钱包介绍 15 | 16 | ### 1.Mist 17 | 18 | 说到以太坊钱包,第一个要说的当然就是Ethereum官方钱包+浏览器Mist。Mist是一个全节点钱包(全节点钱包通俗的来说就是同步了全部的以太坊区块信息的钱包)。也就是说打开钱包后,电脑会自动同步全部的以太坊区块信息,但是同步现有以太坊公链数据的时间比较长。Mist在开发的时候也可以在你的私链上使用,关于Mist的使用,后面咱们会写相关的文章进行介绍。 19 | 20 | ### 2.Parity 21 | 22 | Parity的目标是成为最快,最轻,最安全的以太坊客户端。 使用最尖端的Rust编程语言来开发Parity。 Parity获得GPLv3许可,可用于以太坊的钱包需求。原以太坊基金会部分成员,开发的钱包。功能强大,也是一个全节点钱包。 23 | 24 | ### 3.MyEtherWallet 25 | 26 | MyEtherWallet 作为一个轻钱包,上手难道不大,无需下载,在直接在网页上就可以完成所有的操作。在MyEtherWallet上生成的私钥由用户自我保管,平台方并无备份。 27 | 28 | ### 4.imToken 29 | 30 | 移动端钱包,操作简便,容易上手,功能齐全,在imToken上生成的钱包私钥保存在手机本地,平台方并无备份。 31 | 32 | ### 5.MetaMask 33 | 34 | MetaMask是一款在谷歌浏览器Chrome上使用的插件类型的以太坊钱包,该钱包不需要下载,只需要在谷歌浏览器添加对应的扩展程序即可,非常轻量级,使用起来也非常方便。 35 | 36 | ### 6.ledger 37 | 38 | Ledger是一个硬件钱包,是一个安全存储私钥的硬件设备,查看钱包和发送交易时,硬件钱包需要与软件钱包配合才能使用。你既可以使用Ledger自己开发的软件钱包,也可以使用其他团队开发的软件钱包。以以太币为例,你可以配合以太币网页钱包myetherwallet.com或者Parity钱包使用Ledger。 39 | 40 | ## 钱包重要的概念 41 | 42 | 区块链钱包中比较重要的名词有:地址、密码、私钥、助记词、keystore。 43 | 44 | 若以银行账户为类比,这5个词分别对应内容如下: 45 | 46 | * 地址=银行卡号 47 | * 密码=银行卡密码 48 | * 私钥=银行卡号+密码 49 | * 助记词=银行卡号+密码 50 | * Keystore=加密私钥 51 | * Keystore+密码=私钥 52 | 53 | ### 1.地址 54 | 55 | 地址=银行卡号 56 | 57 | ### 2.密码 58 | 59 | 密码=银行卡密码 60 | 61 | 密码的用途有两个,一是转账时候的支付密码,二是用 keystore 导入钱包时的登录密码。如果对原密码进行修改,有两种方法,一是直接修改密码,这需要输入原密码。如果原密码忘记了,用助记词或私钥导入钱包,同时设置新密码。 62 | 63 | ### 3.私钥 64 | 65 | 私钥=银行卡号+密码 66 | 67 | 创建钱包后,输入密码可以导出私钥,这个私钥属于明文私钥,由 64 位字符串组成,一个钱包只有一个私钥且不能修改。在导入钱包中,输入私钥并设置一个密码,就能进入钱包并拥有这个钱包的掌控权,就可以把钱包中的代币转移走。 68 | 69 | ### 4 助记词 70 | 71 | 助记词=银行卡号+密码 72 | 73 | 创建钱包后,会出现一个备份助记词功能,选择备份助记词,输入密码,会出现 12 个单词,每个单词之间有一个空格,这个就是助记词,一个钱包只有一个助记词且不能修改。助记词是私钥的另一种表现形式,具有和私钥同样的功能,在导入钱包中,输入助记词并设置一个密码,就能进入钱包并拥有这个钱包的掌控权,就可以把钱包中的代币转移走。助记词只能备份一次,备份后,在钱包中再也不会显示,因此在备份时一定要抄写下来。 74 | 75 | ### 5.keystore 76 | 77 | keystore=加密私钥 78 | keystore+密码=私钥 79 | 80 | 钱包里有一个备份keystore功能,选择备份 keystore,输入密码,会出现一大段字符,这个就是 keystore。在导入钱包中,选择官方钱包,输入keystore和密码,就能进入钱包了。需要说明的是,这个密码是本手机原来设置的本钱包密码, 这一点和用私钥或助记词导入钱包不一样,用私钥或助记词导入钱包,不需要知道原密码,直接重置密码。keystore 属于加密私钥,和钱包密码有很大关联,钱包密码修改后,keystore 也就相应变化,原来备份的 keystore 也就失去了作用,因此,若是修改了钱包密码,就需要重新备份 keystore。 81 | 82 | -------------------------------------------------------------------------------- /wallet/mist/README.md: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------