├── .gitignore ├── README.md ├── book.json ├── content ├── README.md ├── SUMMARY.md ├── acknowledgement.md ├── cardano │ ├── cardano-introduction.md │ └── ouroboros.md ├── ethereum │ ├── design-rationale.md │ ├── ethereum-casper-101-glossary.md │ ├── ethereum-casper-101.md │ ├── ethereum-pos-overview-casper-ffg.md │ ├── ethereum-sharding-overview-and-finality.md │ ├── how-does-ethereum-work.md │ └── proof-of-stake-how-i-learned-to-love-weak-subjectivity.md ├── fundamentals │ ├── blockchain-consensus-attacks.md │ ├── byzantine-fault-tolerance.md │ └── explain-pow.md ├── images │ ├── 127313-00906f6529d95f84.png │ ├── 127313-2130de02ce98ecbd.png │ ├── 127313-2404bdeafef81129.png │ ├── 127313-3761d41e37f5a6de.png │ ├── 127313-3a69614f0c028914.png │ ├── 127313-3c4174ad7cf6edf3.png │ ├── 127313-3d3318619b99e25b.png │ ├── 127313-4187c6080889b8cb.png │ ├── 127313-48dab933ae6ea057.png │ ├── 127313-4a458b4eef3b5b6c.png │ ├── 127313-4f8e668c826fd31a.png │ ├── 127313-500001f22e8ed4a6.png │ ├── 127313-557a7b4ee36264f8.png │ ├── 127313-5e6c75f6cea7fa92.png │ ├── 127313-60b2cac4fb9fccfc.png │ ├── 127313-63cf06f07825bec7.png │ ├── 127313-6aa6cff5d863d496.png │ ├── 127313-6b87ca4b980e26ea.png │ ├── 127313-6f6c93920b44ffa1.png │ ├── 127313-7916a022a9b78649.png │ ├── 127313-8d156e10bade5a17.png │ ├── 127313-9138ceb26bc263b1.png │ ├── 127313-94c92c07edce0330.png │ ├── 127313-98fa6d44aad3701f.png │ ├── 127313-996c857601ed80a1.png │ ├── 127313-9c708d3c3d6a19c2.png │ ├── 127313-9caab2339f93b153.png │ ├── 127313-db16fcaa52e1ecc9.png │ ├── 127313-e29a813637a274cf.png │ ├── 127313-e9b0730b1798704d.png │ ├── 127313-e9c65514b54cbaf1.png │ ├── 127313-ec45a7fca855f2e0.png │ ├── 127313-ef9eedc8340e754a.png │ ├── 127313-f4f76e554a475825.png │ └── 127313-f6c7eaf781fac7f4.png ├── iota │ └── iota_consensus_v1.0.md ├── misc │ ├── articles.md │ ├── bitcoin-fork.md │ ├── blockchain-consensus.md │ ├── dpos-consensus-algorithm-this-missing-white-paper.md │ ├── how-the-bitocin-protocol-actually-works.md │ ├── on-public-and-private-blockchains.md │ ├── others │ │ ├── list.md │ │ └── ouroboros.md │ └── the-blockchain-economy-a-beginners-guide-to-institutional-cryptoeconomics.md ├── monero │ └── what-is-monero.md ├── orchid │ └── orchidprotocol-whitepaper.md ├── part-1 │ ├── basic-prototype.md │ └── src │ │ ├── Makefile │ │ ├── block.go │ │ ├── blockchain.go │ │ └── main.go ├── part-2 │ ├── proof-of-work.md │ └── src │ │ ├── Makefile │ │ ├── block.go │ │ ├── blockchain.go │ │ ├── main.go │ │ ├── proofofwork.go │ │ └── utils.go ├── part-3 │ ├── persistence-and-cli.md │ └── src │ │ ├── Makefile │ │ ├── block.go │ │ ├── blockchain.go │ │ ├── cli.go │ │ ├── commands.go │ │ ├── main.go │ │ ├── proofofwork.go │ │ └── utils.go ├── part-4 │ ├── img │ │ └── blockchain-info-tx.png │ ├── src │ │ ├── Makefile │ │ ├── block.go │ │ ├── blockchain.go │ │ ├── cli.go │ │ ├── main.go │ │ ├── proofofwork.go │ │ ├── transaction.go │ │ └── utils.go │ └── transactions-1.md ├── part-5 │ └── address.md ├── part-6 │ └── transactions-2.md ├── part-7 │ └── network.md ├── polkadot │ └── lightpaper.md └── solidity.md ├── docs ├── Consensus.md ├── Cryptography.md ├── General.md ├── README.md └── Web3.md └── styles └── website.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | !Makefile 10 | 11 | blockchain.db 12 | 13 | .DS_Store 14 | 15 | _book/ 16 | node_modules/ 17 | 18 | ### Above combination will ignore all files without extension ### 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Opinioned Blockchain Reading List

2 |

A collection of blockchain resources.

3 | 4 | Wiki 维护了一个阅读列表: [Opinioned Blockchain Reading List](https://github.com/liuchengxu/blockchain-tutorial/wiki), 我会整理一些值得阅读研究的资源,大部分是英文的,欢迎贡献对应的中文资源。 5 | 6 | ---- 7 | 8 | Blockchain Tutorial 9 | =================== 10 | 11 | :point_right: [目录](content/SUMMARY.md) 12 | 13 | :point_right: 使用 [gitbook](https://liuchengxu.gitbooks.io/blockchain-tutorial/content/) 阅读(可能需要翻墙) 14 | 15 | 本教程最初是译自 [Jeiwan/blockchain_go](https://github.com/Jeiwan/blockchain_go) 的系列文章。随着对区块链认识的不断深入,我也会不断地进行补充和丰富。大家也可以关注我的其他平台账号,我会将一些觉得有内容的文章整理到这里: 16 | 17 | - 简书:[liuchengxu](https://www.jianshu.com/u/daf68451f175) 18 | - CSDN: [liuchengxu_](http://blog.csdn.net/simple_the_best) 19 | - segmentfault: [liuchengxu](https://segmentfault.com/u/liuchengxu) 20 | 21 | 因为比较喜欢简书的编辑器,所以可能新文章会先出现在简书,然后“搬运”到其他平台:)。 22 | 23 | ## Donation 24 | 25 | - BTC: `1FLctnA5iRqba3cuc1xUACuAaAVWKqjUwr` 26 | - BCH: `qp2vp4ev0aygatuv4mnxnhc5vevu5susjsj6f8wwp6` 27 | - ETH: `0xBE5d431e7D2273340F6e76061C05bb42Bc39103d` 28 | 29 | ## Contributions 30 | 31 | - 勘误,更好的翻译,解释等等 32 | - 其他优秀的文章 33 | 34 | ## Resources 35 | 36 | Python 版本的 [Jeiwan/blockchain_go](https://github.com/Jeiwan/blockchain_go): [blockchain-py](https://github.com/yummybian/blockchain-py) 37 | 38 | ### Python 39 | 40 | - [write-your-own-blockchain](https://bigishdata.com/2017/10/17/write-your-own-blockchain-part-1-creating-storing-syncing-displaying-mining-and-proving-work/) 41 | 42 | ### Golang 43 | 44 | - [gocoin](https://github.com/piotrnar/gocoin) 45 | - [gochain](https://github.com/crisadamo/gochain) 46 | - [blockchain](https://github.com/izqui/blockchain) 47 | - [naivechain](https://github.com/kofj/naivechain) 48 | 49 | ## Credits 50 | 51 | - [blockchain_go](https://github.com/Jeiwan/blockchain_go) 52 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "区块链系列教程", 3 | "description": "从基本原型,工作量证明,到交易,网络等各部分的区块链系列教程", 4 | "author": "liuchengxu", 5 | "output.name": "site", 6 | "language": "zh-hans", 7 | "gitbook": "3.2.3", 8 | "root": "./content", 9 | "links": { 10 | "sidebar": { 11 | "Home": "https://github.com/liuchengxu/blockchain-tutorial" 12 | } 13 | }, 14 | "styles": { 15 | "website": "styles/website.css" 16 | }, 17 | "plugins": [ 18 | "-lunr", 19 | "-search", 20 | "-highlight", 21 | "-livereload", 22 | "search-plus@^0.0.11", 23 | "simple-page-toc@^0.1.1", 24 | "github@^2.0.0", 25 | "github-buttons@2.1.0", 26 | "edit-link@^2.0.2", 27 | "prism@^2.1.0", 28 | "prism-themes@^0.0.2", 29 | "advanced-emoji@^0.2.1", 30 | "anchors@^0.7.1", 31 | "include-codeblock@^3.0.2", 32 | "ace@^0.3.2", 33 | "emphasize@^1.1.0", 34 | "katex@^1.1.3", 35 | "splitter@^0.0.8", 36 | "mermaid-gb3@2.1.0", 37 | "tbfed-pagefooter@^0.0.1", 38 | "expandable-chapters-small@^0.1.7", 39 | "sectionx@^3.1.0", 40 | "local-video@^1.0.1", 41 | "anchor-navigation-ex@0.1.8", 42 | "favicon@^0.0.2", 43 | "alerts@^0.2.0", 44 | "include-csv@^0.1.0", 45 | "puml@^1.0.1", 46 | "-sharing", 47 | "sharing-plus@^0.0.2", 48 | "image-captions", 49 | "donate" 50 | ], 51 | 52 | "pluginsConfig": { 53 | "theme-default": { 54 | "showLevel": true 55 | }, 56 | "prism": { 57 | "css": [ 58 | "prism-themes/themes/prism-base16-ateliersulphurpool.light.css" 59 | ] 60 | }, 61 | "github": { 62 | "url": "https://github.com/liuchengxu/blockchain-tutorial" 63 | }, 64 | "github-buttons": { 65 | "repo": "liuchengxu/blockchain-tutorial", 66 | "types": [ 67 | "star", 68 | "watch", 69 | "fork" 70 | ], 71 | "size": "small" 72 | }, 73 | "include-codeblock": { 74 | "template": "ace", 75 | "unindent": true, 76 | "edit": true 77 | }, 78 | "sharing": { 79 | "douban": false, 80 | "facebook": false, 81 | "google": true, 82 | "hatenaBookmark": false, 83 | "instapaper": false, 84 | "line": false, 85 | "linkedin": false, 86 | "messenger": false, 87 | "pocket": false, 88 | "qq": false, 89 | "qzone": false, 90 | "stumbleupon": false, 91 | "twitter": true, 92 | "viber": false, 93 | "vk": false, 94 | "weibo": false, 95 | "whatsapp": false, 96 | "all": [ 97 | "facebook", "google", "twitter", 98 | "weibo", "instapaper", "linkedin", 99 | "pocket", "stumbleupon", "qq", "qzone" 100 | ] 101 | }, 102 | "tbfed-pagefooter": { 103 | "copyright": "Copyright © liuchengxu 2017-2018", 104 | "modify_label": "该文件修订时间:", 105 | "modify_format": "YYYY-MM-DD HH:mm:ss" 106 | }, 107 | "simple-page-toc": { 108 | "maxDepth": 3, 109 | "skipFirstH1": true 110 | }, 111 | "edit-link": { 112 | "base": "https://github.com/liuchengxu/blockchain-tutorial/edit/master", 113 | "label": "Edit This Page" 114 | }, 115 | "sitemap-general": { 116 | "prefix": "http://blockchain.liuchengxu.org" 117 | }, 118 | "anchor-navigation-ex": { 119 | "isRewritePageTitle": false, 120 | "tocLevel1Icon": "fa fa-hand-o-right", 121 | "tocLevel2Icon": "fa fa-hand-o-right", 122 | "tocLevel3Icon": "fa fa-hand-o-right" 123 | }, 124 | "sectionx": { 125 | "tag": "b" 126 | }, 127 | "favicon": { 128 | "shortcut": "favicon.ico", 129 | "bookmark": "favicon.ico" 130 | }, 131 | "image-captions": { 132 | "caption": "_PAGE_LEVEL_._PAGE_IMAGE_NUMBER_ -- _CAPTION_" 133 | }, 134 | "donate": { 135 | "wechat": "https://raw.githubusercontent.com/liuchengxu/img/master/gitbook/wechat.jpeg", 136 | "alipay": "https://raw.githubusercontent.com/liuchengxu/img/master/gitbook/alipay.jpeg", 137 | "title": "", 138 | "button": "赞赏 👍 ", 139 | "alipayText": "支付宝打赏", 140 | "wechatText": "微信打赏" 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /content/README.md: -------------------------------------------------------------------------------- 1 | 区块链教程 2 | ========== 3 | 4 | 本教程内容涉及: 5 | 6 | - 用 golang 从零开始构建区块链(Bitcoin)系列 7 | - 区块链基础知识 8 | - Ethereum 9 | - Cardano 10 | - Orchid 11 | - Polkadot 12 | - ...... 13 | 14 | 实际上,本教程也是我对于区块链认识的一个剪影。区块链不仅仅是计算机科学,还涉及了政治经济制度,社会分工协作等等很多方面,因此我的关注点不仅在于深度,更在于其广度,更多是站在研究的角度,而非仅仅是一个程序员的视角。 15 | 16 | :warning: *在这里打个广告:如果对智能合约感兴趣的话,可以看一下 [zastrin.com](https://cn.zastrin.com/) 的智能合约培训,中文版是我翻译的,在学习过程中有问题的话也可以问我。* 17 | 18 | ## Donation 19 | 20 | - BTC: `1FLctnA5iRqba3cuc1xUACuAaAVWKqjUwr` 21 | - BCH: `qp2vp4ev0aygatuv4mnxnhc5vevu5susjsj6f8wwp6` 22 | - ETH: `0xBE5d431e7D2273340F6e76061C05bb42Bc39103d` 23 | -------------------------------------------------------------------------------- /content/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ---- 4 | 5 | * [前言](README.md) 6 | 7 | ### Bitcoin 8 | 9 | * [基本原型](part-1/basic-prototype.md) 10 | * [工作量证明](part-2/proof-of-work.md) 11 | * [持久化和命令行接口](part-3/persistence-and-cli.md) 12 | * [交易(1)](part-4/transactions-1.md) 13 | * [地址](part-5/address.md) 14 | * [交易(2)](part-6/transactions-2.md) 15 | * [网络](part-7/network.md) 16 | 17 | ### Ethereum 18 | 19 | * [设计原理](ethereum/design-rationale.md) 20 | * [以太坊分片:overview and finality](ethereum/ethereum-sharding-overview-and-finality.md) 21 | * [以太坊 Casper 101](ethereum/ethereum-casper-101.md) 22 | * [以太坊 Casper 101 词汇表](ethereum/ethereum-casper-101-glossary.md) 23 | 24 | ### Cardano 25 | 26 | * [卡尔达诺入门必备](cardano/cardano-introduction.md) 27 | * [ouroboros](cardano/ouroboros.md) 28 | 29 | ### Menero 30 | 31 | * [什么是门罗币?终极入门指南](monero/what-is-monero.md) 32 | 33 | ### IOTA 34 | 35 | * [IOTA 交易,确认和共识](iota/iota_consensus_v1.0.md) 36 | 37 | ### Misc 38 | 39 | - [区块链共识](misc/blockchain-consensus.md) 40 | - [区块链经济学:制度加密经济学入门指南](misc/the-blockchain-economy-a-beginners-guide-to-institutional-cryptoeconomics.md) 41 | - [PoW 本质上是个去中心化的时钟](fundamentals/explain-pow.md) 42 | - [DPOS 共识算法 - 缺失的白皮书](misc/dpos-consensus-algorithm-this-missing-white-paper.md) 43 | 44 | 45 | 46 | 47 | 48 | ---- 49 | 50 | * [致谢](acknowledgement.md) 51 | -------------------------------------------------------------------------------- /content/acknowledgement.md: -------------------------------------------------------------------------------- 1 | - 本教程演绎自 [blockchain_go](https://github.com/Jeiwan/blockchain_go) 的系列文章 2 | - gitbook 配置参考了 [gitbook-use](https://github.com/zhangjikai/gitbook-use) 3 | -------------------------------------------------------------------------------- /content/cardano/cardano-introduction.md: -------------------------------------------------------------------------------- 1 | 卡尔达诺入门必备 2 | ================ 3 | 4 | 本文为卡尔达诺(Cardano)的入门读物,主要内容为卡尔达诺的整个项目概览: 5 | 6 | - 卡尔达诺是什么,为什么,有何独特之处,以及团队组织。 7 | - ADA 是什么,乌洛波罗斯(Ouroboros)共识。 8 | 9 | ## 卡尔达诺 10 | 11 | ### 是什么,为什么 12 | 13 | 卡尔达诺是一个去中心化的公有区块链和加密货币项目,它目前正在开发一个智能合约平台,该平台旨在提供更多超越以往任何协议的高级功能。它是第一个衍生自科学哲学的区块链平台,主要以研究驱动,开发团队网罗了全球顶尖的工程师和研究人员。 14 | 15 | 通俗来讲,卡尔达诺以**可证的权益证明共识(provable PoS)**乌洛波罗斯(Ouroboros)为中心,集比特币(Bitcoin),以太坊(Ethereum,智能合约),波卡(Polkadot,跨链)之大成。卡尔达诺的智能合约将使用第三代基于 LLVM 的虚拟机 [IELE](https://iohk.io/blog/iele-a-new-virtual-machine-for-the-blockchain),通过侧链来实现跨链功能。 16 | 17 | 卡尔达诺的最初目标是希望可以改善当前加密货币的设计与开发模式,最终愿景是希望可以提供一个更加平衡,且可持续发展的生态系统,并满足用户寻求其他系统整合的需求。 18 | 19 | ### 有何独特之处 20 | 21 | 卡尔达诺是目前第一个通过学术界同行评审的区块链项目,可以说在行业中是只此一家,绝无仅有。另一独特之处是,Cardano 是由 Haskell 语言实现,Haskell 被认为最安全的编程语言之一,它可以将错误的发生机率降至最低,同时为平台的安全性提供保证。当然了,也有人认为使用 Haskell 反而是个劣势,因为作为一个小众语言,这让开发门槛高了不少,可能会有因此让不少 ADA 开发爱好者 “报国无门”。 22 | 23 | ### 团队组织 24 | 25 | 先来说一下相关组织。卡尔达诺的背后主要有三个组织。第一个是创立于瑞士的卡尔达诺基金会,它是一个非营利组织。卡尔达诺基金会的核心使命是培育、促进发展与教育卡尔达诺用户和商业社区,接洽监管与当局商业事务。卡尔达诺背后的第二个实体组织是 IOHK,这是一家加密货币研发的先驱公司,该公司开发卡尔达诺平台的合约一直持续到 2020 年。卡尔达诺项目的第三个业务合作伙伴是 Emurgo,该公司为创业投资咨询公司, 同时协助企业建立卡尔达诺区块链系统。 26 | 27 | 再来谈一下团队,用个词来说,就是高端大气上档次。在这里仅简单介绍两个人,更多内容可自行点击 [team](https://iohk.io/team/) 查看。 28 | 29 | [Aggelos Kiayias](https://iohk.io/team/aggelos-kiayias/) 是爱丁堡大学主管网络安全和隐私的教授,在相关学术期刊和会议上发表过超过 100 篇论文,[这里](https://scholar.google.ae/citations?user=P_L_vZAAAAAJ&hl=zh-CN&oi=ao) 是他的 Google scholar 主页,有兴趣的可以去了解一下。他是 ouroboros 白皮书的主要作者,也是 IOHK 的首席科学家。 30 | 31 | [Philip Wadler](https://iohk.io/team/philip-wadler/) 是爱丁堡大学理论计算机科学系的教授,他参与贡献了 Haskell,Java 和 XQuery 语言的设计,也是<>,<< XQuery from the Experts (Addison Wesley, 2004)>> 和 <> 等著作的合著者之一。[这里](https://scholar.google.ae/citations?user=Iz-3VFQAAAAJ&hl=zh-CN&oi=ao) 是他的 Google scholar 主页,引用数超过 20000,在学术界也是泰斗无疑了。 32 | 33 | ## 什么是 ADA 34 | 35 | 每个公有链都有一个代币(token),ADA 就是是卡尔达诺区块链上的代币。ADA 是卡尔代币区块链平台上的基础代币,如果有侧链,侧链也会有自己的代币。 36 | 37 | ## 什么是乌洛波罗斯(Ouroboros) 38 | 39 | 卡尔达诺采用一种革命性的新权益证明(PoS)算法,称为乌洛波罗斯(ouroboros),它决定了各个节点如何达成网络一致性。该算法是整个基础架构的关键所在,是区块链技术的重大创新。 40 | 41 | 目前大多区块链采用的是 PoW 共识,但是该共识有不少缺点,比如资源浪费。在工作量证明(PoW)中,矿工投入运算能力来竞争下一个块的出块权。PoW 的关键在于解决了一个随机的“领导人选择(leader election)”问题,也就是选出一个矿工来出下一个块。 42 | 43 | 在权益证明中,依照区块链账本中股权者所拥有权益的比例,随机选取选择下ㄧ个出块人。为了确保区块链的安全性,选择股权者来产生区块的方法必须是真随机的。为了实现领导者选举(leader election)过程的随机性,乌洛波罗斯的创新是通过安全、多方执行掷硬币协议来达成这点。 44 | 45 | 实际上,权益证明的概念由来已久,也有不少项目已经采用了该算法,但是已有的 PoS 有着诸多缺陷,并且无法证明其安全性。乌洛波洛斯是第一个安全性经过形式化证明的权益证明共识,它由 IOHK 首席科学家 [Aggelos Kiayias](https://iohk.io/team/aggelos-kiayias/) 教授领导的团队设计而成,并且通过了学术界同行的一致评审,论文在 [这里](https://eprint.iacr.org/2016/889.pdf),更多文章也在下面的学术论文链接中找到。 46 | 47 | ## 其他 48 | 49 | 由于卡尔达诺的相关资源实在是过于分散,我们在这里也做了简单的总结: 50 | 51 | 官方网站: 52 | 53 | - [cardanohub](https://www.cardanohub.org/en/home) 54 | - [input | output](https://iohk.io/) 55 | - [emurgo](http://emurgo.io/) 56 | - [cardanofoundation](https://cardanofoundation.org/) 57 | 58 | Twitter: 59 | 60 | 1. [Input Output](https://twitter.com/InputOutputHK) 61 | 2. [Cardano Foundation](https://twitter.com/CardanoStiftung) 62 | 3. [Cardano Community](https://twitter.com/cardanocom) 63 | 4. [ADA](https://twitter.com/ADAcoin_) 64 | 5. [emurgo](https://twitter.com/emurgo_io) 65 | 66 | 论文文档: 67 | 68 | - [学术论文](https://www.cardanohub.org/en/academic-papers/) 69 | - [卡尔达诺清算层文档](https://cardanodocs.com/introduction/) 70 | 71 | GitHub: 72 | 73 | - [cardano-sl](https://github.com/input-output-hk/cardano-sl) 74 | - [input out community](https://github.com/input-output-hk) 75 | 76 | 团队: 77 | 78 | - [iohk team](https://iohk.io/team/) 79 | 80 | 参考资料: 81 | 82 | [1] https://www.cardanohub.org/ 83 | -------------------------------------------------------------------------------- /content/cardano/ouroboros.md: -------------------------------------------------------------------------------- 1 | [The Bitcoin Backbone Protocol: Analysis and Applications](https://eprint.iacr.org/2014/765.pdf) 2 | 3 | 4 | # Ouroboros: A Provably Secure Proof-of-Stake Blockchain Protocol 5 | 6 | - Proof of Stake 7 | - Proof of physical resources(e.g., proof of work) 8 | 9 | 10 | First, we provide a model that formalizes the problem of realizing a PoS-based blockchain protocol. The model we introduce is in the spirit of [24], focusing on persistence and liveness, 11 | 12 | - *persistence* 13 | - *liveness* 14 | 15 | 16 | coin-flipping protocol ==> produce the randomness for the leader election process 17 | 18 | 19 | Also, unique to our approach is the fact that the system ignores round-to-round stake modifications. Instead, a snapshot of the current set of stakeholders is taken in regular 20 | intervals called epochs; in each such interval a secure multiparty computation takes place utilizing the blockchain itself as the broadcast channel. 21 | 22 | 23 | Specifically, in each epoch a set of randomly selected stakeholders form a committee which is then responsible for executing the coin-flipping protocol. The outcome of the protocol determines the set of next stakeholders to execute the protocol in the next epoch as well as the outcomes of all leader elections for the epoch. 24 | 25 | 26 | - a snapshot of the current set of stakeholders is taken in regular intervals called epochs 27 | 28 | - in each such interval a secure multiparty computation takes place utilizing the blockchain itself as the broadcast channel 29 | 30 | 31 | Our protocol is secure under a number of plausible assumptions: 32 | (1) the network is synchronous in the sense that an upper bound can be determined during which any honest stakeholder is able to communicate with any other stakeholder, 33 | (2) a number of stakeholders drawn from the honest majority is available as needed to participate in each epoch, 34 | (3) the stakeholders do not remain offline for long periods of time, 35 | (4) the adaptivity of corruptions is subject to a small delay that is measured in rounds linear in the security parameter (or alternatively, the players have access to a sender-anonymous broadcast channel). 36 | 37 | Sleepy consensus considers a fixed stakeholder distribution (i.e., stake does not evolve over time) and targets a “mixed” corruption setting, where the adversary is allowed to be adaptive as well as perform fail-stop and recover corruptions in addition to Byzantine faults. 38 | 39 | Snow White [7] addresses an evolving stakeholder distribution and uses a corruption delay mechanism similar to ours for arguing security. Nevertheless, contrary to our protocol, the Snow White design is susceptible to a “grinding” type of attack that can bias high probability events in favor of the adversary 40 | 41 | Algorand 42 | 43 | - [Ouroboros: A Provably Secure Proof-of-Stake Blockchain Protocol](https://eprint.iacr.org/2016/889.pdf) 44 | -------------------------------------------------------------------------------- /content/ethereum/design-rationale.md: -------------------------------------------------------------------------------- 1 | ### 账户模型与 UTXO 2 | 3 | 比特币,及其很多的继任者,将用户的余额存储在一个基于 UTXO(unspent transaction output)的数据结构中,系统的整个状态由一个“未花费输出,unspent output” 的集合构成。那么 UTXO 到底是什么呢?简单来说,UTXO 就是人民币,就是 “coin”。有多少 UTXO,就有多少人民币。跟人民币的区别在于: 4 | 5 | - “面值”,人民币的面值有 1 元,5 元,20 元,100 元等等是固定的,但是理论上,UTXO 的“面值”可以使任意正数。 6 | - 新的人民币由政府的印钞机产生,新的 UTXO 由交易产生。 7 | 8 | 每个 UTXO 都有一个所有者和一个值,一笔交易会花费一些 UTXO,并创造出一些新的 UTXO,使用 UTXO 模型的交易在验证上有限制: 9 | 10 | 1. 引用的每个输入必须是有效的,而且还没有被花费 11 | 2. 对于每个输入,交易必须有一个跟输入所有者匹配的签名 12 | 3. 总输入必须大于或等于总输出 13 | 14 | 在 UTXO 系统中,一个用户的“余额”实际上并不直接存在,而是通过计算得来。一个用户有一个私钥,这个私钥生成一个有效的签名,这个签名能够解锁的所有币的总和就是“余额”。 15 | 16 | 以太坊摒弃了 UTXO 模型,采用了一种更简单的方式:账户模型。 17 | 18 | 19 | https://github.com/ethereum/wiki/wiki/Design-Rationale/37be78fa7b726d36156c537b757c874b7aa5705 20 | -------------------------------------------------------------------------------- /content/ethereum/ethereum-casper-101-glossary.md: -------------------------------------------------------------------------------- 1 | 以太坊 Casper 101 2 | ================= 3 | 4 | > 原文: [Ethereum Casper 101](https://medium.com/@jonchoi/ethereum-casper-101-7a851a4f1eb0) 5 | 6 | ## 词汇表 7 | 8 | [**Proof of Stake**](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ) -- 公有链的一类共识算法,基于验证人(validator)在网络中的经济权益(economic stake)。 9 | 10 | [**Casper**](https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/) -- 以太坊的 PoS 研究和项目。 11 | 12 | **Finality** - 系统中的操作一旦完成,就永远再无法回滚([Vitalik on settlement finality](https://medium.com/@jonchoi/an%20attack%20wherein%20a%20reputation%20system%20is%20subverted%20by%20forging%20identities%20in%20peer-to-peer%20networks))背景:在 PoW 中,finality(最终确定性) 是概率性且隐式的。Casper 所设计的机制,是显式强制保证最终确定性。 13 | 14 | [**Fork Choice Rule**](https://medium.com/@VitalikButerin/minimal-slashing-conditions-20f0b500fc6c) - 分叉选择规则(fork choice rule)就是一个函数,由客户端进行评估,输入为区块或是其他生成的信息,向客户端输出“标准链(canonical chain)”是什么。 15 | 16 | [**Slashing Conditions**](https://medium.com/@VitalikButerin/minimal-slashing-conditions-20f0b500fc6c) - 通过在 P2P 网络中伪造身份,进而破坏一个信誉系统(reputation system)的攻击。 17 | 18 | [**3 E’s of Sybil Resistance**](https://twitter.com/dominic_w/status/648330685963370496) - 1. 进入成本(entry cost)2. 存在成本(existence cost)3. 退出惩罚(exit penalty)。(源自 [Dominic Williams](https://medium.com/@dominic_w))。 19 | 20 | [**Nothing-at-stake problem**](https://ethereum.stackexchange.com/questions/2402/what-exactly-is-the-nothing-at-stake-problem) - 一个 PoS 实现上的挑战,它指的是如果出现分叉,无法同时验证两条链。这是 PoS 的一个老大难问题,但是通常也被认为是可解决的。比如,参见 [Slasher](https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/)。 21 | 22 | **Bribe attack(贿赂攻击)** - 攻击者用贿赂的方式,改变验证人博弈论框架的纳什均衡,以此来削弱协议的安全性。( 更多背景见 [History of Capser pt 2](https://blog.ethereum.org/2016/12/07/history-casper-chapter-2/)) 23 | 24 | [**Long range attack(长程攻击)**](https://blog.ethereum.org/2014/05/15/long-range-attacks-the-serious-problem-with-adaptive-proof-of-work/) - 与 51% 攻击(为了攻击者的利益让一个更长的链重写账本)的机制一样,但并不是发起 6 个块的攻击,而是攻击更多块(比如 60,000 个块)。 25 | 26 | [**DAG**](https://en.wikipedia.org/wiki/Directed_acyclic_graph) - “有向无环图(Directed Acyclic Graph)”。一个没有有向环的有限有向图。([ETH Stack Exchange](https://ethereum.stackexchange.com/questions/1993/what-actually-is-a-dag)) 27 | 28 | **GHOST ** - “Greedy Heaviest Observed Subtree”. 它是一个链的选择规则,目的是快速确认,同时尽量减少在安全性或去中心化上的妥协。([原始论文](https://eprint.iacr.org/2013/881.pdf),[ETH GHOST](https://github.com/ethereum/wiki/wiki/White-Paper#modified-ghost-implementation)) 29 | 30 | **Synchronicity** - 指的是消息的时间假设(也就是说,同步,部分同步还是异步)。 31 | 32 | **Liveness** - “availability(可用性)”。遵守协议的节点最终决定出块。其反面就是网络状态会因为出块决定而阻塞(也就是说,没有 2/3 投票高度的 Tendermint) 33 | 34 | **Safety** - “correctness”。遵守协议的节点会就出块达成共识。另一个直观解释是两个冲突的块是否可以提交。 35 | 36 | [**FLP Impossibility Theorem(FLP 不可能定理)**](https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf) - “不可能有一个 live,safe,asynchronous 的网络”(已被证明) 37 | 38 | **Accountable Faults** - 由于某一个验证人,或者某一些验证人引起的错误。 39 | 40 | [**Byzantine Faults(拜占庭错误)**](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)/Byzantine Behavior -- 对于对于不同观察者显示出不同症状的任意错误。非协议遵守行为。 41 | 42 | **Byzantine Failure(拜占庭故障)** -- 在一个需要 [consensus](https://en.wikipedia.org/wiki/Consensus_%28computer_science%29) 的系统中,由于拜占庭错误引发的系统服务损失。 43 | 44 | [**Byzantine Fault Tolerance**](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)(“BFT”)-- 一个系统能够容忍拜占庭错误的能力。1/3 的拜占庭错误阈值在异步网络中。1/2 在同步网络中。(BFT 共识算法包括 Paxos,PBFT 和 Casper ,Tendermint). 45 | 46 | **Nakamoto Consensus** -- 类似比特币就 PoW 的共识。此外,Nakamoto 风格的共识存在,将会是基于链的 PoS 而不是基于 BFT 的 PoS。 47 | 48 | [**Tendermint**](https://tendermint.com/static/docs/tendermint.pdf) -- 专注一致性(consistency)的 PoS 实现。在少于 1/3 恶意参与者的情况下,永远不会分叉,但是缺点是,如果缺乏 2/3 的验证人投票,链可能会停止运转。 49 | 50 | **Validator(验证人)** -- 一个为了获取奖励而验证 checkpoint 或是区块的 entity,是 PoS 中的 “矿工”。 51 | 52 | **Validator Set(验证人集合)** -- 一条链在任意给定时间的验证人集合。 53 | 54 | **Checkpoint** -- 在 FFG 中,它是按照规律(比如每 50 个块)隔开的一个块,这个块在底层是 PoW 的链之上,同时采用 PoS 验证机制(比如 Ethereum with ethash)。 55 | 56 | **Epoch** -- 在 FFG 中,50 个块为一个周期,验证人可以对周期的最后一个块(也就是 checkpoint)进行投票。PoW 矿工出块,PoS 验证人在每个周期对 checkpoint 进行验证。 57 | 58 | **Dynamic Validator Sets(动态验证人集合)** -- 在一个时期内,一条链可以有一个变化的验证人集合。在 BFT 风格的共识算法中,这是一个巨大的改进。Tendermint 是第一个突破,Casper 也正在这一点上积极运作。 59 | 60 | **Equivocation** -- 验证人发送两个相互冲突的消息( 更准确的定义见 [slide 28 of this deck](https://ethereumfoundation.org/devcon2/wp-content/uploads/2016/10/A-Correct-by-Construction-Asynchronous-Casper-Protocol.pdf)) 61 | 62 | **Dunkles** -- 包含从非主导(non-dominant)块向主导(dominant)块数据的机制。它提供了一个更好的激励机制,可以有效缓解 nothing-at-stake 问题([link](https://ethereum.stackexchange.com/questions/19146/are-there-still-uncles-with-proof-of-stake))。 63 | 64 | **Proposal Mechanism(提案机制)** -- 集合中的验证人提议哪个块进行公正(justification)或最终确定(finalization)的机制。 65 | 66 | **Justification(公正)** -- 比如在 FFG 中,如果验证人集合中有 2/3 的人对一个新的 checkpoint 投票,那么该 checkpoint 就是准确的记录。这是形成一个最终确定化(finalized)的 checkpoint 的中间过程. 67 | 68 | **Finalization(最终确定)** -- 比如在 FFG 中,如果 2/3 的验证人对一个被公正过的 checkpoint 投票,那么这个 checkpoint 就是准确的记录。这一步完成后,checkpoint 被最终确定。 69 | 70 | [**State Transition System(状态转移系统)**](https://github.com/ethereum/wiki/wiki/White-Paper#bitcoin-as-a-state-transition-system) -- 一个维护给定状态(比如交易或账户集合)和它随着时间(也就是转移,transition)变化的系统。比特币,以太坊和其他公有链可以被认为是状态转移系统。 71 | 72 | **Protocol Utility Function(协议效用函数)** -- 一个告诉我们协议当前执行状况的公式,理想状态下应该是从区块链内部进行计算的(calculable from inside the blockchain)。在 PoW 的链中,它可以是所有生成的区块占主链的百分比。在 Casper 中,如果协议效用(protocol utility)是 0,表示执行完美,每个 epoch 都被最终确定,也没有发生安全故障。对每个没有最终确定的 epoch,会有一些惩罚,而对于每个安全故障会有一个非常严重的惩罚。如果一个协议效用函数可以被形式化,那么故障惩罚就可以尽可能与这些故障所造成的协议损失相匹配。(来自 [Triangle of Harm](http://vitalik.ca/general/2017/07/16/triangle_of_harm.html)) 73 | -------------------------------------------------------------------------------- /content/ethereum/ethereum-casper-101.md: -------------------------------------------------------------------------------- 1 | 以太坊 Casper 101 2 | ================= 3 | 4 | > 原文: [Ethereum Casper 101](https://medium.com/@jonchoi/ethereum-casper-101-7a851a4f1eb0) 5 | 6 | 7 | ## 引言 8 | -------------------------------------------------------------------------------- /content/ethereum/ethereum-pos-overview-casper-ffg.md: -------------------------------------------------------------------------------- 1 | 以太坊 PoS 概览:Casper FFG 2 | =========================== 3 | 4 | >出处: [Ethereum PoS Overview: Casper FFG](https://docs.google.com/presentation/d/1fqnjL-2TqXjhHx8k7HRX7eUYnDK83adnlCLLH8Bk054/edit#slide=id.g29703948a2_0_2965) 5 | 6 | 简单介绍了区块链概念、工作量证明协议的工作原理,以及工作量证明的缺点。 7 | 8 | ## 2. PoW 的缺点 9 | 10 | 1. 浪费资源。 11 | 12 | 2. 易受 ASIC 和中心化的攻击 13 | 14 | 3. “最终确定性(finality)” 不足 15 | 16 | 介绍 Casper FFG 的基本概念和原理,如:权益、检查点、投票以及最终化。 17 | 18 | ### "Final Gadget" 是什么 19 | 20 | Casper the Friendly Finality Gadget 是以太坊上 PoS 的第一个部署阶段,主要内容有: 21 | 22 | 1. 在 PoW 之上加了一层 PoS 23 | 2. 每 50 个块(一个 epoch)实现终态(finality) 24 | 3. 抵抗 51% 攻击 25 | 4. 相对于 PoW 减少能源浪费 26 | 27 | 在后续的迭代升级中,PoW 会被完全替代。 28 | 29 | ### 验证人 30 | 31 | #### 成为验证人 32 | 33 | 任何 ETH 的持有者只要将 ETH 存入 Casper 智能合约,就可以成为验证人。 34 | 35 | Casper 的激励逻辑存在于合约内。 36 | 37 | #### 为什么收取保证金 38 | 39 | - 给“好人”以更大的经济激励 40 | - 给“坏人”以更大的经济惩罚 41 | 42 | 通过保证金强制惩罚恶意参与者。 43 | 44 | 整条链以 50 个块进行分隔,叫做一个 epoch,第 50 个块为 checkpoint。 45 | 46 | 当两个连续的 checkpoint 得到 2/3 的投票,就最终确定,不可修改。justified checkpoint. 47 | 48 | 49 | 介绍 Casper FFG 的惩罚条件。 50 | 51 | 介绍 Casper FFG 的经济激励。 52 | 53 | 介绍第五章中的经济激励机制如何能够遏制恶意攻击,以及 Casper 协议下一步的方向。 54 | -------------------------------------------------------------------------------- /content/ethereum/ethereum-sharding-overview-and-finality.md: -------------------------------------------------------------------------------- 1 | 以太坊分片:overview and finality 2 | ================================= 3 | 4 | > 原文:https://medium.com/@icebearhww/ethereum-sharding-and-finality-65248951f649 5 | 6 | 在 [Ethereum Casper 101](https://medium.com/@jonchoi/ethereum-casper-101-7a851a4f1eb0)[1] 中,Jon Choi 对 Casper 做了一个很棒很清晰的综述,并解释了*为什么*显式最终确定性(explicit finality)对于可扩展性(scalability)大有裨益。本文旨在给出一个以太坊分片的设计概览,并阐释显式最终确定性*如何*有助于区块链分片。 7 | 8 | 为了完全理解以太坊分片机制提案的技术规范,我强烈推荐深入研究 Vitalik 写的 [**sharding doc**](https://github.com/ethereum/sharding/blob/develop/docs/doc.md) [2]. 9 | 10 | ## 区块链可扩展性问题 11 | 12 | 1. **不断增长的交易**。 13 | 14 | 2. **目前的块生成过程导致可扩展性受限**。区块的 *gas limit* 束缚了区块的计算容量。无论是提高区块的 gas 上限,还是大大降低区块时间,都会导致高陈腐率(high stale rate),并削弱网络对抗攻击的能力。 15 | 16 | 3. **并行不足**。首先,现有的 EVM 按先后顺序依次处理交易。其次,出于安全和去中心化的考虑,每个全节点会执行每一笔交易,并存储整个(或修剪后)的状态树。 17 | 18 | >进阶阅读:并行执行交易 [EIP 648 — Easy parallelizability](https://github.com/ethereum/EIPs/issues/648) 19 | 20 | 为了解决可扩展性问题,分片(sharding)引入了链上状态分区(on-chain state partition)来获得更高的吞吐量。 21 | 22 | 23 | ## 术语 24 | 25 | 首先,让我们来看一下在主链(你可以理解为现在的 Mainnet chain)和分片链(shard chain)上不同层次的对象区别: 26 | 27 | ![Table 1. Terminology.](../images/127313-8d156e10bade5a17.png) 28 | 29 | 可以简单地这么认为,交易都会被装入 “collation”。与区块类似,一个 collation 也会指向它在链(指的是分片链)上的 parent collation。 成为一个 “collator”,就意味着你有资格在 POS 分片链上提名一个新的 collation。 30 | 31 | ![Figure 1. A glimpse of basic collation data structure.](../images/127313-3a69614f0c028914.png) 32 | 33 | ## 基本的二次分片 34 | 35 | ### 分片链的共识依赖于主链 36 | 37 | 与侧链类似,collation 只有**一小部分的证明**必须记录在主链上 -- 这也是我们如何扩展区块链的基本想法: 38 | 39 | 1. 分片链上的交易处于自己独立的空间中,分片验证人(shard validator)*只*需要验证他们所关注的分片。 40 | 41 | 2. 分片链也通过 POS 机制依附于主链,以获得 *更高层次的共识(higher level of consensus)*。 42 | 43 | ### 验证人管理员合约(Validator Manager Contract, VMC) 44 | 45 | 为了将分片链加入到主链中,在主链上需要有一个叫做验证人管理员合约(VMC)的特殊合约。VMC 是这个分片机制的核心。VMC 的目的可以概括如下: 46 | 47 | 1. **权益证明系统**。如果验证者表现不端,其权益将会被剥削。 48 | 49 | 2. **伪随机采样**。通过将当前块哈希作为种子,采样出合格的 collator。基本上,验证者将它们的保证金(stake)存入 VMC,然后他们的验证代码地址(validation code address)将会被记录在一个 VMC 内部的 **全局验证人列表(a global validators pool list)**。系统将会从验证人列表中采样出一个分片链的验证人,并将其指定为指定**“时期(period,下面会解释什么是 period)”**内,指定分片的验证人。这种方式使得验证者无法提前预测他们何时会成为验证者,也无法预测会成为哪个分片的验证人。 50 | 51 | 3. **Collation header 验证**。VMC 有一个 `addHeader(bytes collationHeader)` 函数,该函数用=来验证 collation header,并记录有效的 collation header hash。这个函数提供了即时的 **链上** 验证。 52 | 53 | 4. **跨分片通信(cross-shard communication)**。利用 UTXO 模型,并通过在主链上进行交易和创建一个 receipt(带有 receipt ID),用户可以将以太存入一个指定分片。分片链上的用户可以给定 receipt ID 创建一个消费 receipt(receipt-consuming)的交易,来花费该 receipt。 54 | 55 | 5. **链上治理(on-chain governance)**。将 VMC 作为议会,使得验证人可以在链上进行投票。 56 | 57 | ## 如何在分片内提名 Collation? 58 | 59 | 在阶段 1,VMC 将会维护 100 个分片(`SHARD_COUNT = 100`)。每个分片**并行**执行,分片 *i* 的客户端只需要验证分片 *i* 上的交易。 60 | 61 | “时期(period)”被定义为一个区块时间的准备窗口(a bounding a window of block times),比如 `PERIOD_LENGTH = 5` 意味着每个周期有 5 个块。这表明在每个周期内,对于每个分片只有不超过 **1** 个有效的 collation。 62 | 63 | ![Figure 2 (a). Quadratic sharding. The proofs of shard states would be recorded on main chain VMC.](../images/127313-48dab933ae6ea057.png) 64 | 65 | 一旦验证人被采样为合格的 collator 来提案一个新的 collation,collator 必须对最近的 collation 进行验证,并发送一笔交易来调用 `addHeader` 函数。注意,如果 collator **周期 10** 被采样到提交一个新的 collation,这意味着 `addHeader` 交易 **必须被包含在周期 10 里面**,也就是说,交易必须在区块号 `10 * PERIOD_LENGTH` 到区块号 `(10 + 1) * PERIOD_LENGTH - 1` 之间. 66 | 67 | ![Figure 2 (b). For one shard, only one collation per period; one block can include multiple addHeader transactions of different shards.](../images/127313-557a7b4ee36264f8.png) 68 | 69 | 70 | collation header hash 必须被记录在 VMC 上,以证明它的 header 全局有效。此外,分片的所有其他验证人必须时刻检测 VMC 以获得最新状态,然后验证交易是否也有效。 71 | 72 | ## 分片链的分叉选择规则(fork choice rule) 73 | 74 | 在基本分片中,分叉选择规则依赖于最长主链。给定一个分片,它的有效 head collation **不是**简单的“最长有效分片链(longest valid shard chain)”的 head collation,而是“**在最长有效主链里面的最长有效分片链(the longest valid shard chain within the longest valid main chain)**”。 75 | 76 | Figure 3(a) 中有个例子,主链上有两个分叉,在下图中第二条链是最长有效主链。因为 `block B3` 是 head block,很容易看出 `collation C3` 是 head collation。 77 | 78 | ![Figure 3 (a).](../images/127313-2130de02ce98ecbd.png) 79 | 80 | 然后 Figure 3(b) 中 `block B3'` 到了。假设 `block B3` 的得分(score)高于 `block B3'`,那么上面的链仍然是最长主链: 81 | 82 | ![Figure 2 (b).](../images/127313-00906f6529d95f84.png) 83 | 84 | 最后 Figure 3(c) 到了 `block 4`。注意到,对于这个分片,虽然 `collation C3` 的得分比 `collation C2` 更高,但是下方的链是**最长有效主链**,所有现在 `collation C2` 是 head collation: 85 | 86 | ![Figure 3 (c)](../images/127313-63cf06f07825bec7.png) 87 | 88 | >更多内容:另一个设计 -- Vlad Zamfir 的 [sharded fork choice rule](https://twitter.com/VladZamfir/status/945358660187893761) 89 | 90 | ![An ingenious design for guaranteeing blocks atomicity before they are finalized.](../images/127313-6f6c93920b44ffa1.png) 91 | 92 | ## 可扩展性与安全性之权衡 93 | 94 | 对于 **去中心化**,**可扩展性** 和 **安全性** 这三个属性,区块链系统最多只能三选其二。 95 | 96 | *— Blockchain Trilemma* in [Sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ#this-sounds-like-theres-some-kind-of-scalability-trilemma-at-play-what-is-this-trilemma-and-can-we-break-through-it) [3] 97 | 98 | 出于对系统安全的保证导致了可扩展性受限 [3]。当为了提高 TPS(每秒交易数)将交易分配到各分片的同时,我们随之也减少了每笔交易的计算资源。 99 | 100 | 分片的其中一个重要机制就是,**如何在链上生成随机数**。 101 | 102 | - collator 被选中的几率,应该仅与验证者的保证金相关,且成比例。 103 | 104 | - 如果验证人能够预测,或是任意选择他们想要参与的分片,那么不诚实的验证人既可以相互共谋,展开一个适应性攻击(adaptive attack)。 105 | 106 | 如果采样不能以较高的随机性进行选择,那么攻击者很可能在分片中展开 **1% 攻击**:如果有 100 个分片,攻击者可以专注于攻击某*一个*分片,他们只需要 1% 的hash rate(POW)/deposit(POS) 就可以控制分片[4]. 107 | 108 | ![Figure 4. Traditional majority attack (51 % Attack)](../images/127313-9138ceb26bc263b1.png) 109 | 110 | ![Figure 5. Sharding 1% attack](../images/127313-94c92c07edce0330.png) 111 | 112 | ## 分片的区块链显式最终确定性 113 | 114 | ### 隐式最终确定性 v.s. 显式最终确定性 115 | 116 | 首先,我必须声明,分片机制应该同时能够应用于 POW 与 POS 的链。即使如此,显式最终确定性这个小物件也跟 Casper 一样,可以使得分片更加健壮。 117 | 118 | 在一般的 POW 链中,最终确定性是概率性,隐式的[1][5]。简单来说,即使区块获得了数以千次的确认,仍有可能对链进行重写。相反,将 [Casper the Friendly Finality Gadget (“FFG”)](https://github.com/ethereum/research/blob/master/papers/casper-basics/casper_basics.pdf) 加密经济机制应用于 POS ,显式地在协议内(in-protocol)强制保证对我们来说,是否是最终确定 (we-can-check-if-its-finalized-for-us)。 119 | 120 | [来自 Vlad] 协议内显式最终确定性阈值有个经济风险:它在 `2/3 + 1` 和 `1/3 + 1` 之间创造了一个理想的 卡特尔大小(cartel size)。相应地,任何不在 `2/3 + 1` 同盟节点(coalition),其最终确定性的边际贡献就将为 0。 121 | 122 | ### 主链最终确定性依赖 123 | 124 | 在基本分片中,分片链锚定与主链之上。 125 | 126 | 对于分片验证人,我们希望分片,区块链容量在阶段 1 扩容 100 倍,因此这 100 个分片的所有验证人,都将需要监测 VMC 状态来获得正确有效的 head collation。对于验证人来说,重要的一点是,要尽快确信他们是否是 collator。对于普通用户而言,如果我们在阶段 2 应用跨分片交易,普通用户也将需要在 VMC 上检索他们的保证金信息(*receipt ID*)。 127 | 128 | 显式最终确定性,将会有助于缓解主链与大量分片链之间同步的不确定性。 129 | 130 | ### 显式最终确定性有助于无状态客户端 131 | 132 | 无状态客户端的基本原则是,它不存储整个状态树,相反,无状态客户端**只存储状态树根**。归档客户端(archival clients)存储整个状态树,并提供给定 collation **所需** 的 Merkle 分支。有了这些 Merkle 分支,无状态客户端就能够构建**部分的状态树**,并验证 collation [6]。 133 | 134 | 一旦完成验证人采样并再混洗(reshuffle),就会立刻触发同步。有了无状态客户端机制,**再混洗**(也就是改变验证者检测的分片,并同步分片链)的成本低至(接近)0,因为它们**只需要验证最新的 collation(也就是有着最高得分的 collation)来同步分片**。 135 | 136 | ![Figure 6. Stateless client model](../images/127313-4a458b4eef3b5b6c.png) 137 | 138 | 因为同步过程可能非常快,无状态客户端模型就可能在每个 collation 之间再混洗成。这不仅会减轻存储压力和开销,也会使系统更安全,因为频繁采样能够获得适应性攻击的抵抗能力。 139 | 140 | Casper FFG 将会提供显式最终确定性阈值[after about 2.5 “epoch times”](http://ethresear.ch/t/casper-contract-and-full-pos/136/2),也就是说,125 个区块时间[1][7]。如果在再混洗期间,验证人能够验证超过 `125 / PERIOD_LENGTH = 25` collation,分片系统能够从显式最终确定性中受益,确信从现在起的前 25 个 collation 可以最终确定。 141 | 142 | 当然了,如果在同步时有更多 collation 得到验证,就会更加安全。 143 | 144 | ## 结束语 145 | 146 | 希望我已经对当前以太坊的分片设计概念作出了简单介绍,以及显式最终确定性如何有益于分片机制。如果想要深入了解协议设计,请访问 [ETHResear.ch](https://ethresear.ch/) 和 [sharding doc](https://github.com/ethereum/sharding/blob/develop/docs/doc.md)。 147 | 148 | 如有任何错误,或是表述不清,欢迎指正! 149 | 150 | 特别感谢 Vitalik Buterin 的杰出工作,Jon Choi 督促我写下这篇文章,Dr.Chang-Wu Chen 修缮,Brian Chen 给予反馈 以及 Vlad Zamfir 的分享。 151 | 152 | 参考: 153 | 154 | [1] Jon Choi. Casper 101: [https://medium.com/@jonchoi/ethereum-casper-101-7a851a4f1eb0](https://medium.com/@jonchoi/ethereum-casper-101-7a851a4f1eb0) 155 | 156 | [2] Vitalik Buterin. Sharding Document:[https://github.com/ethereum/sharding/blob/develop/docs/doc.md](https://github.com/ethereum/sharding/blob/develop/docs/doc.md) 157 | 158 | [3] Vitalik Buterin and the contributors. Sharding FAQ:[https://github.com/ethereum/wiki/wiki/Sharding-FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ) 159 | 160 | [4] Vitalik Buterin. Sharding Mindmap:[https://www.mindomo.com/mindmap/sharding-d7cf8b6dee714d01a77388cb5d9d2a01](https://www.mindomo.com/mindmap/sharding-d7cf8b6dee714d01a77388cb5d9d2a01) 161 | 162 | [5] Vitalik Buterin. On Settlement Finality:[https://blog.ethereum.org/2016/05/09/on-settlement-finality/](https://blog.ethereum.org/2016/05/09/on-settlement-finality/) 163 | 164 | [6] Ethresear.ch thread — The Stateless Client Concept:[https://ethresear.ch/t/the-stateless-client-concept/172](https://ethresear.ch/t/the-stateless-client-concept/172) 165 | 166 | [7] Ethresear.ch thread — Casper contract and full POS:[https://ethresear.ch/t/casper-contract-and-full-pos/136/2](https://ethresear.ch/t/casper-contract-and-full-pos/136/2) 167 | -------------------------------------------------------------------------------- /content/ethereum/how-does-ethereum-work.md: -------------------------------------------------------------------------------- 1 | 2 | ## 区块链定义 3 | 4 | - 密码学安全(Cryptographically secure):通过复杂的数学算法保证数字货币的安全,这些算法很难破解。这使得很难对系统作弊,比如创建虚假交易,擦除已有交易,等等。 5 | - 单机交易(transactional singleton macine):对于系统中创建的所有交易,仅有一台电脑负责。换句话送,“真相只有一个”。所以有世界电脑的说法,整个世界就是一台超级电脑。 6 | - 共享状态(shared-state):在这台世界电脑上存储的状态是共享的,每个人都可获得。 7 | 8 | ## 以太坊区块链 9 | 10 | 以太坊区块链本质上是一个 **基于交易的状态机** 。 11 | 12 | 13 | [1] https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369 14 | -------------------------------------------------------------------------------- /content/ethereum/proof-of-stake-how-i-learned-to-love-weak-subjectivity.md: -------------------------------------------------------------------------------- 1 | >原文:[Proof of Stake: How I Learned to Love Weak Subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/) 2 | -------------------------------------------------------------------------------- /content/fundamentals/blockchain-consensus-attacks.md: -------------------------------------------------------------------------------- 1 | # 区块链共识级别的攻击 2 | 3 | ## Double spending attacks 4 | 5 | 推翻已经确认的交易。 6 | 7 | - persistence 8 | 9 | ## Grinding attacks 10 | 11 | 恶意节点试图影响 slot leader selection 的过程,加大自己被选中为 slot leader 的概率。 12 | 13 | 这种攻击主要是由于随机性来源于链本身的原始数据,比如块头和交易,恶意节点可以尝试多个可能成为 slot leader 的 block header。 14 | 15 | ## Transaction denial attacks 16 | 17 | 阻止一笔交易被确认。恶意节点监控某个特殊的账户,不让它发交易。 18 | 19 | liveness 20 | 21 | ## Desynchronization attacks 22 | 23 | 诚实节点无法与网络同步。 24 | 25 | ## Eclipse attacks 26 | 27 | ## 51% attacks 28 | 29 | ## Bribery attacks 30 | 31 | - Short Range attack 32 | 33 | Long-Range attack + checkpoint = short range attack 34 | 35 | ## Long-range attacks 36 | 37 | 所谓长程攻击,指的是恶意节点试图从创世块开始对链进行分叉,分叉链可能或包含于主链不同的交易和块,因此也叫 **Alternative History** 或者 **History Revision** 攻击。Long Range, Alternative History, Alternate History, History Revision 在很多情况下是通用的概念。 38 | 39 | 长程攻击之所以存在的主要原因是 Weak Subjectivity。 40 | 41 | ### Weak Subjectivity 42 | 43 | 这个问题主要会影响刚刚加入到网络的新节点和长时间离线后重新加入的节点。 44 | 45 | 当一个新节点刚刚加入网络时,创世块肯定是有的,也是毫无争议的。但是除了创世块,节点还会看到目前有好几条链 -- 主链与分叉链,即使并非恶意分叉,PoW 在末端自身也极易分叉。然后,节点并不能从这些链中判断出哪个是主链。 46 | 47 | 长时间离线后重新加入的节点也是如此。PoS 也是最长链原则,拥有最多块数的叉将成为主链。在 PoW 中,要投入大量算力才能获取出块权,对于 PoW 来说,最长链十分有效。 48 | 49 | 在 PoW 中,除非遭受 51% 攻击,否则不可能从创世块开始分叉。要伪造跟主链同样长度的分叉,需要投入大量的算力。因此,最长链原则已经可以解决 Weak Subjectivity 问题。 50 | 51 | ### Costless Simulation 52 | 53 | 对于 PoS 来说,最长链就不够用了,因为这里又出现了一个叫 Costless Simulation 的问题。PoS 的验证人不需要像矿工一样必须要进行大量计算,验证人只需要从交易池中取出交易,打包入块,最后广播出去。因此伪造跟主链一样长的链,毫无成本可言。 54 | 55 | Weak Subjectivity 加上 Costless Simulation 导致最长链原则对 PoS 不再适用,长程攻击由此产生。 56 | 57 | ### Long Range Attacks 58 | 59 | 1. simple 60 | 2. posterior corruption ==> Key Evolving Signature Scheme 61 | 3. stake bleeding 62 | 63 | - https://blog.positive.com/rewriting-history-a-brief-introduction-to-long-range-attacks-54e473acdba9 64 | - [Stake-Bleeding Attacks on Proof-of-Stake Blockchains](https://eprint.iacr.org/2018/248.pdf) 65 | 66 | ## Nothing at stake attacks 67 | 68 | 当发生分叉时,验证人的最优策略是承认每一条分叉,并在所有分叉上继续挖。这样无论哪一条叉最终胜出,验证人都能获得奖励。 69 | 70 | - https://en.wikipedia.org/wiki/Proof-of-stake 71 | - https://blog.goldmint.io/nothing-at-stake-and-longrange-attack-in-pos-4ec486f1fc89 72 | - https://github.com/ethereum/wiki/wiki/Problems 73 | - https://pivx.org/nothing-considered-a-look-at-nothing-at-stake-vulnerability-for-cryptocurrencies/ 74 | - https://medium.com/@abhisharm/understanding-proof-of-stake-through-its-flaws-part-2-nothing-s-at-stake-8d12d826956c 75 | - https://medium.com/coinmonks/understanding-proof-of-stake-the-nothing-at-stake-theory-1f0d71bc027 76 | 77 | ## Past majority attacks 78 | 79 | nothing at stake 的一种特殊情况。 80 | 81 | ## Selfish-mining 82 | 83 | 自私挖矿,恶意挖到块以后,并不广播出去,而是偷偷藏在手里,然后继续挖。当监听到有节点挖出同样高度的块时,迅速将手里的块广播出去,如果恶意节点的网络更好, 那么恶意节点手中同高度的块就会被接受,如此一来,恶意节点就会更有优势挖到下面的块。 84 | 85 | ## Reference 86 | 87 | - Ouroboros 88 | -------------------------------------------------------------------------------- /content/fundamentals/byzantine-fault-tolerance.md: -------------------------------------------------------------------------------- 1 | 2 | - 无解的两将军问题 3 | - 拜占庭将军问题 4 | - 拜占庭容错 5 | 6 | ## 两将军问题 7 | 8 | 1975 年首次发表 http://hydra.infosys.tuwien.ac.at/teaching/courses/AdvancedDistributedSystems/download/1975_Akkoyunlu,%20Ekanadham,%20Huber_Some%20constraints%20and%20tradeoffs%20in%20the%20design%20of%20network%20communications.pdf , 1978 年该问题正式命名为两将军问题(two generals problem)。说的是两个将军商量攻击一个共同的敌人,将军 1 为主,将军 2 为从。无论是将军 1 还是 2,仅凭其自身军队,谁也无法打败敌军。因此,他们必须合作,并在同一时间点同时发起进攻。这个问题看似很简单,却有一个避不开的问题: 9 | 10 | 将军 1 为了要告诉将军 2 进攻时间,必须要派信使穿过敌方军营通知将军 2。但是,信使可能会被敌方截获,因此信息也就无法送达将军 2。这就会导致将军 1 进攻的时候,将军 2 按兵不动,那么攻击就会失败。 11 | 12 | 即使将军 1 的信使将进攻时间顺利送达,将军 2 还必须要进行回应,告诉将军 1 确实收到了信息。这有点像三方的 [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol)。那么又会出现上面的问题,信使可能会被敌军抓获。这就会造成无限的 ACK,两个将军永远也无法达成共识。 13 | 14 | 两将军问题已经被证明是[无解](https://en.wikipedia.org/wiki/Two_Generals%27_Problem#Proof)的。 15 | 16 | 17 | ## 拜占庭将军问题 18 | 19 | [ Lamport, Shostak and Pease 1982](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.126.9525&rep=rep1&type=pdf),这是两将军问题的广义版本。还是同一个场景,不同的地方是现在不只有两个将军,而是有更多的将军。此外还有更复杂的一点,这些将军里面可能有叛徒,叛徒会发出错误信心,比如说 9 点攻击,但是实际 9 点并不会攻击。 20 | 21 | 22 | 对于任意 m,如果有超过 3m 个将军,至多 m 个叛徒,算法 OM(m) 达成共识。 23 | 24 | 这表明只要有 2/3 的参与者是诚实的,那么算法就能达成共识。而一旦叛徒超过 1/3,就无法达成共识,无法阻止进攻,敌军自然胜利。 25 | 26 | 27 | >参与者的目标是选择一个大部分都同意的同一个决定,而不是指定某一个决定。 28 | 29 | 30 | 7 个将军,两个叛徒,更多内容 [这里](http://marknelson.us/2007/07/23/byzantine/) 31 | 32 | 参考: 33 | 34 | [1] https://medium.com/loom-network/understanding-blockchain-fundamentals-part-1-byzantine-fault-tolerance-245f46fe8419 35 | -------------------------------------------------------------------------------- /content/fundamentals/explain-pow.md: -------------------------------------------------------------------------------- 1 | PoW 本质上是个去中心化的时钟 2 | ============================ 3 | 4 | > 原文:[Blockchain Proof-of-Work Is a Decentralized Clock](https://grisha.org/blog/2018/01/23/explaining-proof-of-work/) 5 | > 6 | > 原文从区块链如何保持交易有序的基本问题出发,对该问题进行了详细阐述,并提出 PoW 本质上是实现了一个“时钟”的观点,这个时钟的一个滴答对应的就是 PoW 算出一次的解。 7 | 8 | 本文解释了比特币 PoW(Proof-of-Work, 工作量证明) 的关键要素,尤其对 PoW 来说不可或缺的一个特性,同时也表明关于 PoW 经常谈到的一些其他特性其实是次要作用,比如安全性,这些次要效应有用,但是非必要。 9 | 10 | 要想理解本文,首先要懂得在区块链中,PoW 是如何工作的一些有趣的属性,这些属性并不那么直观,甚至可以说相当反直觉,比如参与者如何在*从来没有相互交流*的情况下,共同地求解一个问题。 11 | 12 | 当理解了这些属性,你应该能够得出一个结论:PoW 的机制主要实现了一个分布式,去中心化的时间系统,即一个时钟。 13 | 14 | 注意本文并非关注 PoW 算法本身的细节,而是探究区块链如何“严丝合缝”地将 PoW 运用其中。如果你还没听过 PoW,请先阅读 [这里](https://en.bitcoin.it/wiki/Proof_of_work)。 15 | 16 | ## 分布式账本时间排序问题 17 | 18 | 在讲解决方案之前,先来关注问题本身。很多 PoW 的相关资料都很令人费解,因为它们常常在没有阐明问题的情况下,就试图讲清楚解决方案。 19 | 20 | 毫无疑问,任何账本都需要有序。你不能发费还没有收到的钱,也不能花费已经花出去的钱。区块链交易(或者说包含交易的块)必须有序,无歧义,同时无需可信的第三方。 21 | 22 | 即便区块链不是一个账本,而是就像日志一样的数据,对于所有节点来说,如果要想共同保有一份完全相同的区块链副本,有序也是必不可少的。交易顺序不同,就是不同的两条链。 23 | 24 | 但是,如果交易是由全世界的匿名参与者生成,也没有中心化机构负责给交易排序,那又如何实现这一点呢?有人会说,交易(或者块)可以包含时间戳,但是这些时间戳又如何可信呢? 25 | 26 | 时间是一个[人类概念](http://www.preposterousuniverse.com/blog/2015/04/03/the-reality-of-time/),时间的任何来源,比如一个原子时钟,就是一个“可信第三方”,除此之外,由于网络延迟和[相对论效应](http://www.astronomy.ohio-state.edu/~pogge/Ast162/Unit5/gps.html),时钟的大部分时间都有轻微误差。很遗憾,在一个去中心化系统中,不可能通过时间戳来决定事件的先后顺序。 27 | 28 | 我们所关心的“时间”并不是所熟悉的年,月,日这种概念。我们需要的是这样一种机制,它可以用来确认一个事件在另一个事件之前发生,或者可能并发发生。 29 | 30 | 首先,为了建立之前与之后的概念,首先必须要建立一个*时间点*的概念。理论上来说,建立一个点时间的概念似乎并不太可能,因为没有技术能够足够精确地测量 [普朗克时间](https://en.wikipedia.org/wiki/Planck_time)。但是你会看到,比特币通过创建属于自己的时间概念变相解决了这个问题,使得确立精确的时间点概念在事实上成为可能。 31 | 32 | [Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport) 1978 年的论文 [“分布式系统中的时间,时钟和事件顺序”](https://amturing.acm.org/p558-lamport.pdf) 中对这个问题有了详细描述,但是除了“正确同步的物理时钟”,实际上并没有提供一个详细的解决方案。1982 年 Lamport 同样描述了 [“拜占庭将军问题”](https://people.eecs.berkeley.edu/~luca/cs174/byzantine.pdf),中本聪在他早期的一封邮件中 [解释](http://satoshi.nakamotoinstitute.org/emails/cryptography/11/) 了 PoW 如何解决了这个问题,[比特币白皮书](https://bitcoin.org/bitcoin.pdf) 指出“为了在端到端的基础上实现一个分布式的 *时间戳服务器*,我们将会使用一个工作量证明系统”,也表明了它主要解决了时间戳的问题。 33 | 34 | ## 时间是根本问题 35 | 36 | 必须要强调的是,在分布式系统中,*不可能将事件与时间点关联起来*,这是一个尚不可解问题,直到中本聪找到了一个解决方案,才使得分布式账本成为可能。在区块链中还有很多其他的技术细节,但是时间是最基础也是最重要的。没有时间,就没有区块链。 37 | 38 | ## PoW 回顾 39 | 40 | 简而言之,比特币的 PoW 就是 [SHA-2](https://en.wikipedia.org/wiki/SHA-2) 哈希满足特定的条件的一个解,这个解很难找到。通过要求哈希满足一个特定的数字,就确定了一个难度(difficulty),难度的值越小,满足输入的数字越少,找到解的难度就越大。 41 | 42 | 这就是所谓的“工作量证明”,因为满足哈希要求的解非常稀少,这意味着找到这样一个解需要很多试错,也就是,“工作(work)”。而工作也就是意味着*时间*。 43 | 44 | ## 块间无变化 45 | 46 | 链的状态由块所反映,每个新的块产生一个新的状态。区块链的状态每次向前推动一个块,平均每个块 10 分钟,是区块链里面最小的时间度量单位。 47 | 48 | ## SHA 无记忆,无进展 49 | 50 | SHA(Secure Hash Algorithm) 在统计学和概率上以[无记忆性(memoryless)](https://en.wikipedia.org/wiki/Memorylessness) 闻名。对于我们人类而言,无记忆性有点反直觉。所谓无记忆性,就是无论之前发生了什么,都不影响这一次事件发生的概率。 51 | 52 | 关于无记忆性,最好的例子就是抛硬币。如果一个硬币连续 10 次都是正面,那么下一次是反面的可能性会不会更大呢?我们的直觉是会,但是实际上,无论上一次的结果是什么,每次抛硬币正反面都是一半一半的概率。 53 | 54 | 而对于需要无进展(progress-free)的问题,无记忆性又是必要条件。progress-free 意味着当矿工试图通过对 [nonces](https://en.bitcoin.it/wiki/Nonce) 进行迭代解决难题时,每一次尝试都是一个独立事件,无论之前已经算过了多少次,每次尝试找到答案的概率是固定的。换句话来说,每次尝试,参与者都不会离“答案”越近,或者说有任何进展(progress)。就下一次尝试而言,一个已经算了一年的矿工,与上一秒刚开始算的矿工,算出来的概率是一样的。 55 | 56 | 在指定时间内,给定一个难度,找到答案的概率*唯一地由所有参与者能够迭代哈希的速度决定*。与之前的历史无关,与数据无关,只跟算力有关。 57 | 58 | 因此,算力是一个与参与者数量,和那些用来计算哈希设备的速度相关的函数。 59 | 60 | ## SHA 与输入无关 61 | 62 | 在比特币中,输入的是区块头。但是如果给它随机传入一些值,找到一个合适哈希的概率仍然是一样的。无论输入是一个有效的块头,还是 /dev/random 中随机的一些字节,都要花费平均 10 分钟来找到一个解。 63 | 64 | 如果你找到了一个合适的哈希,但是输入却不是一个有效的块头,虽然无法将块上链,但是它仍然是一个工作量证明(即使没啥用)。 65 | 66 | ## 难度属于银河系 67 | 68 | 令人惊奇的是,难度是*universe(全宇宙,或者说通用的)*,也就是说它充满了整个宇宙,无处不在。火星上的矿工也同样能参与挖矿,但是他们不需要感知到地球矿工的存在,也不需要与地球上的矿工有交流,仍然是每 10 分钟就会解决一个“难题”。(好吧,当他们解出难题时,需要告诉地球上的矿工,否则我们永远也不知道)。 69 | 70 | 了不起的是,远距离参与者不需要通过真正的相互交流进行沟通,因为他们在共同地求解同一个统计学问题,并且他们甚至互相感知不到对方的存在。 71 | 72 | “通用属性(universal property)”一开始看起来很神奇,实际上很容易解释。我用了“通用”一词,因为就这一个词即可表达到位,但是它实际指的是“所有参与者都知道(这个难度)”。 73 | 74 | SHA-256 的输入可以是 0 到 2 的 256 方之间的任何一个整数(因为输出是 32 字节,也就是在 0 到 2^256,任何超过该范围的数将会导致冲突,也就是多余)。即使这个集合已经非常大了([比已知宇宙里所有原子总数都大](https://learncryptography.com/cryptanalysis/why-is-2-256-secure)),不过每个参与者都知道这个集合,并且只能从这个集合里选取一个数。 75 | 76 | 如果输入的集合全世界都知道,SHA-256 全世界都知道,难度要求也是全世界都知道了,那么找到一个解的概率自然也就是“全世界都知道(universe)”。 77 | 78 | ## 计算 SHA 即参与 79 | 80 | 如果所述问题是找到一个合适的哈希,那么要想解出这个问题,只需要去试一次,但是,哪怕就试一次,你就已经影响了整个算力。就这次尝试而言,你就已经成为了一个帮助其他人解决问题的参与者。虽然你不需要告诉其他人你“做了”(除非你找到了答案),其他人也不需要知道,但是想要找到解的这次尝试*真真切切地*影响到了结果。对于全宇宙而言,也是如此。 81 | 82 | 如果上面这段话看起来仍然不是那么令人信服,一个很好的类比就是寻找大素数问题。找到最大的素数很难,并且一旦找到,它就是“被发现”或者“已知的”。有无数的素数,但是在全宇宙中,每个数只有一个实例。因此无论是谁试图找到最大素数,就是在解同一个问题,而不是这个问题另一个单独的实例。你不需要告诉其他人你已经打算寻找最大素数,你只需要在找到时通知其他人。如果从来没有人寻找最大素数,那么它永远也不会被找到。因此,只要参与(也就是试图找到素数),即使它正在秘密进行,仍会影响结果,只要公布最后的发现(如果找到)。 83 | 84 | 这就是中本聪设计的精妙之处,他利用了这个令人难以置信的统计学现象,即任何参与都会影响结果,即使秘密进行,即使尚未成功。 85 | 86 | 值得注意的是,因为 SHA 是 progress-free,每一次尝试都可以被认为是一个参与者加入其中,然后立即退出。因此可以这么理解,矿工们来了又走,每秒无数次轮回。 87 | 88 | ## 参与度由统计学揭示 89 | 90 | 这个神奇的秘密参与(secret participation)属性反过来也成立。很多网站上显示的全球算力,并非是由每个矿工在某个“矿工注册办公室”注册,并定期汇报他们的算力而来。并不存在这种事情。 91 | 92 | 因为对于在 10 分钟找到一个指定难度的解,所需算力是已知的,一个人平均必须尝试这么多次(截止成文之时大概 10^21)才能找到答案,无论这个人是谁,他在哪儿。 93 | 94 | 我们不知道这些参与者是谁,他们也从来都不会说我正在参与其中,没有找到解的人(实际上他们都是)从来不会告诉其他人他们正在进行计算,他们可能在世界上任何一个地方,但是我们肯定他们必然存在。因为生活还要继续,问题(找到满足条件的哈希)始终还是要被解决。 95 | 96 | ## 工作(work)是一个时钟(clock) 97 | 98 | 关键之处在于:找到满足条件的哈希的难度,就类似于一个时钟的角色。一个宇宙(universe)时钟,因为全宇宙只有一个这样的时钟,不需要同步,任何人都能“看”到这个时钟。 99 | 100 | 即使这个时钟不精确也没关系。重要的是,对所有人来说,它都是同一个时钟,链的状态与这个时钟的滴答(tick)无歧义地绑定到一起。 101 | 102 | 这个时钟由遍布地球上的未知数量的参与者共同操作,参与者相互之间完全独立。 103 | 104 | ## 谜题的最后一部分 105 | 106 | 解决方案必须是块哈希(准确来说,是块头)。上面已经提到,对于 SHA 来说,输入的内容并不重要,但是如果它是真实的块,那么无论何时找到一个解,它都发生在 PoW 这个时钟的滴答处。没有早一点,没有晚一点,而是恰好在这个点。我们知道这是毫无歧义的,因为块是整个机制的一部分。 107 | 108 | 换句话来说,如果块不是 SHA256 函数的输入,我们仍然有一个分布式时钟,但是我们无法将块绑定到这个时钟的滴答上。将块作为输入就解决了这个问题。 109 | 110 | 值得注意的是,我们的 PoW 时钟只提供了滴答。但是我们没办法从滴答中分出顺序,于是就引入了哈希链(hash chain)。 111 | 112 | ## 分布式共识 113 | 114 | 共识(consensus)意味着意见一致(agreement)。所有参与者别无选择,只能同意“时钟已然滴答”。并且每个人都知道滴答和附加的数据。正如中本聪在邮件里面所解释的,这确实解决了拜占庭将军问题,。 115 | 116 | 在一个罕见却又常见的情况下,会出现共识分离,有两个连续的滴答与一个块有关联,这就发生了冲突。通过哪个块与下一个滴答相关联解决了这个冲突,同时将有争议的块变为“孤儿块(orphan)”。链如何继续是个概率问题(a matter of chance),这也可能间接地归因于 PoW 的时钟。 117 | 118 | ## 就这样 119 | 120 | 这就是区块链的 PoW(工作量证明)。它并不是一个为了让矿工赢得出块权的“乐透”,也不是为了将实际能源转换成一个有价值的概念,这些都偏离了本质。 121 | 122 | 比如矿工奖励的角度来看,虽然这些奖励激励了矿工参与,但是这并不是区块链诞生的必要因素。块哈希形成一条链,但是这与工作量并没什么关系,它是从密码学上强制保证了块的顺序。哈希链使得前一个滴答“更确定”,“更加不可抵赖”,或者简单来说,更安全。 123 | 124 | PoW 也能使块不可更改,这是一种好的副作用,也使得隔离见证(Segregated Witness)成为可能,但是隔离见证也能通过保留签名(见证,witness)实现,所以这也是次要的。 125 | 126 | ## 结论 127 | 128 | 比特被的 PoW 只是一个分布式,去中心化的时钟。 129 | 130 | 如果你理解了这个解释,那么你应该能够更好地理解 PoW 与 PoS 的异同。显然,两者不具有可比性:PoS 是有关于(随机分布的)权力(authority),而 PoW 是一个时钟。 131 | 132 | 在区块链的背景下,PoW 这个名字可能是个误用,起的并不太好。这个词来源于 [Hashcash](https://en.wikipedia.org/wiki/Hashcash) 项目,它确实用于证实工作(work)。在区块链中,它主要关于可验证的花费时间。当一个人发现满足难度的哈希时,我们知道它必然会花费一些时间。实现时间延迟的方法就是“工作”,而哈希就是这段时间的证明。 133 | 134 | PoW 是关于 time 而非 work 的事实也表明,可能存在一些其他的统计学问题,这些问题同样消耗时间,但却需要更少的能源。这也可能意味着比特币算力有些“过分”,因为我们在上面所描述的比特币时钟,在只有部分算力的情况下,也是可信的,这是这种激励结构推动了能源消耗。 135 | 136 | 如果你找到了一个方法能够同步滴答,并且需要更少的工作,这是一个价值万亿美元的问题,请一定要告诉我! 137 | 138 | P.S. 特别感谢 [UChicago Statistics](https://galton.uchicago.edu/) 的 [Sasha Trubetskoy](http://sashat.me/) 对上述文字的 review 和建议。 139 | -------------------------------------------------------------------------------- /content/images/127313-00906f6529d95f84.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-00906f6529d95f84.png -------------------------------------------------------------------------------- /content/images/127313-2130de02ce98ecbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-2130de02ce98ecbd.png -------------------------------------------------------------------------------- /content/images/127313-2404bdeafef81129.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-2404bdeafef81129.png -------------------------------------------------------------------------------- /content/images/127313-3761d41e37f5a6de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-3761d41e37f5a6de.png -------------------------------------------------------------------------------- /content/images/127313-3a69614f0c028914.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-3a69614f0c028914.png -------------------------------------------------------------------------------- /content/images/127313-3c4174ad7cf6edf3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-3c4174ad7cf6edf3.png -------------------------------------------------------------------------------- /content/images/127313-3d3318619b99e25b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-3d3318619b99e25b.png -------------------------------------------------------------------------------- /content/images/127313-4187c6080889b8cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-4187c6080889b8cb.png -------------------------------------------------------------------------------- /content/images/127313-48dab933ae6ea057.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-48dab933ae6ea057.png -------------------------------------------------------------------------------- /content/images/127313-4a458b4eef3b5b6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-4a458b4eef3b5b6c.png -------------------------------------------------------------------------------- /content/images/127313-4f8e668c826fd31a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-4f8e668c826fd31a.png -------------------------------------------------------------------------------- /content/images/127313-500001f22e8ed4a6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-500001f22e8ed4a6.png -------------------------------------------------------------------------------- /content/images/127313-557a7b4ee36264f8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-557a7b4ee36264f8.png -------------------------------------------------------------------------------- /content/images/127313-5e6c75f6cea7fa92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-5e6c75f6cea7fa92.png -------------------------------------------------------------------------------- /content/images/127313-60b2cac4fb9fccfc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-60b2cac4fb9fccfc.png -------------------------------------------------------------------------------- /content/images/127313-63cf06f07825bec7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-63cf06f07825bec7.png -------------------------------------------------------------------------------- /content/images/127313-6aa6cff5d863d496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-6aa6cff5d863d496.png -------------------------------------------------------------------------------- /content/images/127313-6b87ca4b980e26ea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-6b87ca4b980e26ea.png -------------------------------------------------------------------------------- /content/images/127313-6f6c93920b44ffa1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-6f6c93920b44ffa1.png -------------------------------------------------------------------------------- /content/images/127313-7916a022a9b78649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-7916a022a9b78649.png -------------------------------------------------------------------------------- /content/images/127313-8d156e10bade5a17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-8d156e10bade5a17.png -------------------------------------------------------------------------------- /content/images/127313-9138ceb26bc263b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-9138ceb26bc263b1.png -------------------------------------------------------------------------------- /content/images/127313-94c92c07edce0330.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-94c92c07edce0330.png -------------------------------------------------------------------------------- /content/images/127313-98fa6d44aad3701f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-98fa6d44aad3701f.png -------------------------------------------------------------------------------- /content/images/127313-996c857601ed80a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-996c857601ed80a1.png -------------------------------------------------------------------------------- /content/images/127313-9c708d3c3d6a19c2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-9c708d3c3d6a19c2.png -------------------------------------------------------------------------------- /content/images/127313-9caab2339f93b153.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-9caab2339f93b153.png -------------------------------------------------------------------------------- /content/images/127313-db16fcaa52e1ecc9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-db16fcaa52e1ecc9.png -------------------------------------------------------------------------------- /content/images/127313-e29a813637a274cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-e29a813637a274cf.png -------------------------------------------------------------------------------- /content/images/127313-e9b0730b1798704d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-e9b0730b1798704d.png -------------------------------------------------------------------------------- /content/images/127313-e9c65514b54cbaf1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-e9c65514b54cbaf1.png -------------------------------------------------------------------------------- /content/images/127313-ec45a7fca855f2e0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-ec45a7fca855f2e0.png -------------------------------------------------------------------------------- /content/images/127313-ef9eedc8340e754a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-ef9eedc8340e754a.png -------------------------------------------------------------------------------- /content/images/127313-f4f76e554a475825.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-f4f76e554a475825.png -------------------------------------------------------------------------------- /content/images/127313-f6c7eaf781fac7f4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/images/127313-f6c7eaf781fac7f4.png -------------------------------------------------------------------------------- /content/iota/iota_consensus_v1.0.md: -------------------------------------------------------------------------------- 1 | IOTA 交易,确认和共识 2 | ==================== 3 | 4 | >原文:https://github.com/noneymous/iota-consensus-presentation 5 | 6 | ## Tangle 初始状态 7 | 8 | 与区块链技术不同,IOTA 并不是一条有着时间序列概念,每个区块前后相连的链,链中的每个块包含一些交易。在 IOTA 中,每笔交易都可以其他交易连接(所谓连接,就是验证其他交易),并且可并行发生。下面的内容将就如何在 IOTA 中加入交易,验证交易及其共识机制展开。 9 | 10 | ![初始 tangle 状态](../images/127313-7916a022a9b78649.png) 11 | 12 | 上图是 tangle 的一个案例,下面内容都会围绕该图展开。绿色交易代表已经被网络以高确定性(high certainty)地确认,蓝色交易是部分确认,也就是确定性较低。灰色(以及下面的黄色)方框表示还没有任何人验证过的 tip (*tip 有尖端,尾部的意思,比如手指尖就可以用这个词,这里的 tip 表示 tangle 中最新的尚无人验证的交易*)。红色交易,表示有冲突,或无效交易。 13 | 14 | 在上图中,交易 `α` 并非一笔普通交易。它引用了交易 `h` 和 `l`,由于交易 `h` 已经被交易 `l` 引用了, `α` 会选择一个 tip(`l`) 和 一个显然不是 tip 的交易(`h`)。这么做目前似乎并没有问题,网络也允许这样的行为。 15 | 16 | ## 加入一笔交易 17 | 18 | ![加入一笔交易](../images/127313-ef9eedc8340e754a.png) 19 | 20 | 为了向 tangle 中加入一笔新的交易,用户必须从 tangle 中随机挑选出两个 tip(tip 就是尚未确认的交易),并对两个 tip 进行验证。所谓验证,意味着用户需要检查 tip 的签名,即所谓的 PoW,并确保所选的 tip 与之前的任何交易(无论是直接相关还是间接相关)都没有冲突。如果所选的 tip 是合法的,用户就对其进行引用,也就是加入新的交易。 21 | 22 | 如果交易既没有被所选的 tip 直接引用,也没有被间接引用,那么对于当前的验证过程来说,这些交易就是不相关的交易。对于不相关交易,会由其他人或是之后的交易来进行验证,并将它们加入到 tangle 中。 23 | 24 | ## 另一笔交易 25 | 26 | ![另一笔交易](../images/127313-f6c7eaf781fac7f4.png) 27 | 28 | 与此同时(其实不必同时,早一点晚一点都无所谓),另一个用户可能正在一个不同的位置加入新的交易。它选择了 tip `z` 和 `y`。如此一来,它就在更大的范围上验证了已经验证过的同样交易,即 `a` 到 `k`,`m` 到 `n`,加上额外的一些没有在交易 `1` 验证路径上的交易(`l`, `o`, `r`, `t`, `v`, `y` 和 `z`)。 29 | 30 | ## 新的 Tangle 状态 31 | 32 | ![Imgur](../images/127313-e9c65514b54cbaf1.png) 33 | 34 | 交易 `1` 和 `2` 的验证路径有重合之处,我们可以看到有一些交易仅被确认一次,有些交易被确认两次。被当前所有 tip 验证和确认的交易就被认为是完全确认。因此,交易 `n` 进入 tangle 更深一层,现在变成了绿色。从现在开始,随后所有连接到 `1` 与/或 `2` 或者它的孩子,将会保持再验证和再确认的交易状态。 35 | 36 | 我们已经学到了什么? 37 | 38 | - 没有人需要看到和验证所有的交易。每个用户仅需要选择和验证两笔交易及其父交易。如此一来,他们仅验证了 tangle 的一部分而已。当其他用户选择并验证不同的 tip 和路径,完整 tangle 的协同验证就出现了。 39 | 40 | - 在某个时间点以后,一旦一笔交易在 tangle 中进入足够深的位置,无论从最新的 tip 中的任意一个,无论从直接或是间接路径上它都存在。这样的交易就被认为是完全确认,并且会被每一个新的交易再验证,再确认。我们可以认为它被所有用户(和机器)确认,并且确定性很高。 41 | 42 | - 为了对确认进行检查,接收者只需要检查交易是否被已有的所有 tip 直接或间接引用(或者通过一个确定比率,如果确定性更低的话,比如 80%,也可以接受)。这时候就不需要再验证或是其他类似操作了。注意:可能会有上千个 tip。与其检查每个 tip 的父节点,更可能的是选择一个随机样本,并做一个统计评估。 43 | 44 | 注意交易 `n` 还没有被确认,因为现在我们的 tip 比较少。下面会展示更多 tip 的场景。 45 | 46 | ## 确认级别 47 | 48 | ![Imgur](../images/127313-2404bdeafef81129.png) 49 | 50 | 我加入了一些新的 tip 对上例进行了扩展。对于每个新的 tip,它的验证路径都被高亮了。通过颜色,你可以清楚地看到哪些交易被多少 tip 所验证,及其验证等级。 51 | 52 | 一个商家可能会根据自身情况设定个性化的确认/确定等级。如果交易速度比交易价值更重要(比如微支付或零价值支付),又或者发送方是一个朋友,一个人可能会以 75% 的确认等级接受交易。在 75% 的确定等级(3/4 tip)下,交易 `l`, `o`, 和 `t` 可能也会被确认。 53 | 54 | ## 传播延迟 55 | 56 | ![传播延迟](../images/127313-4187c6080889b8cb.png) 57 | 58 | 理论上,由于更慢的 PoW 或者传播延迟,可能在稍后出现一笔慢速交易 `5`。鉴于我们已经知道了交易 `5`,交易 `n` 就不会再被所有的 tip 完全确认。但是,他们的确认确定性(confirmation certainty)仍然很高,有 4/5 tip 确认(实际上会有上千而不是 5 个 tip)。记住,所有一切都是为了一个高概率的确定性 -- 就像在区块链里面,区块的每次确认就是增加了确定性的概率。 59 | 60 | 请注意,本例中的交易 `5` 的状态并非从 “确认” 转变为 “未确认”。它仅是从改变了数学上精确的确定性比率(比如,如果一共有 100 个 tip,从 100% 到 99%)。一旦一些随后的交易引用了交易 `1` 和 `5`,交易 `n` 就会被所有的 tip 再次完全确认。这样小的确认等级变化将不太可能会发生,更进一步的交易会进入 tangle。 61 | 62 | 请注意,100% 的确认/确定等级无论如何都很难达到,因为总会出现一些无正面贡献的 tip(比如,引用一些无用的交易,或是根本不遵守协议)。 63 | 64 | ## 双花 65 | 66 | ![双花](../images/127313-3d3318619b99e25b.png) 67 | 68 | 想象这样一种情况,一个用户在 tangle 的两个不同的地方加入了两笔冲突的交易(`w` 和 `y`)。对于随后的用户,在他们的验证路径上可能只有这些冲突交易里面的其中一笔(取决于他们的 tip 选择,和一些可能的传播时延)。比如,加入了交易 `1` 和 `2` 的用户就不会看到冲突,并会确认他们所选的 tip。因此,双花就得到了第一次确认。但是,迟早必然会发生的是,这两笔冲突的交易会出现在一笔交易的验证路径上。比如,交易 5 就会看到冲突,继而不会确认选出的 tip。相反,为了确保它自身会是一笔有效的交易,它会重新选择 tip 直到找到不冲突的交易。 69 | 70 | 依赖于 tip 的选择和 tangle 的推进,在冲突变得逐渐清晰之前,可能会有更多的用户在 `w` 或 `y` 后面附加交易。取决于用户在哪里附加最多新的交易,`w` 或者 `y` 都会在某个点确认,但是其他会被丢弃。被丢弃交易(因为它们看不到即将到来的冲突)后面的所有交易也会被丢弃。但是,这些交易并不会丢失,而是可能被任何人(但最可能是交易接收方)接受,并为了新的确认机会再次附加到 tangle。这时,PoW 就需要重新来过,但是并不需要从发送方发送新的签名了。 71 | 72 | ## 解决双花 73 | 74 | ![解决双花](../images/127313-e29a813637a274cf.png) 75 | 76 | 上面已经说到,一个用户尝试将交易 `5` 与 tip `1` 和 `2` 相连。由于冲突,它重新进行选择 tip,并且决定连接到 tip `1` 和 `4`。另一个用户(也可能是同一个)选择 tip `2` 和 `3` 连接到交易 7。虽然出现了多个分支,但是由于 `w` 和 `y `的双花,只有一个能够存活。基于 tip 的随机选择(和交易的累积权重),这两个分支的其中一个会接收更多的的子交易(独立的,权重)直到 tangle 进入一个状态,在这个状态里就不可能再合法地附加任一片段。在上面的示例中,用户可能继续与交易 `5`,`6` 和 `8` 相连,但是不会连接到交易 `7`。因此,交易 `y`,`2`,`3` 和 `7` 将永远也不会成为一个完全确认的状态。 77 | 78 | 正如上面所说的,交易 `y`,`2`,`3` 和 `7` 可能被再次加入 tangle 。只要他们(仍然)是有效的,就会新的机会被确认。交易 `2`,`3` 和 `7` 可能然后被确认,但是交易 `y` 仍然无效。 79 | 80 | ## 离线 Tangle 81 | 82 | ![离线 tangle](../images/127313-6b87ca4b980e26ea.png) 83 | 84 | tangle 能够让用户在离线的情况下,仍然能够继续构建交易,比如在公司内部的局域网,或者在断电的情况下与邻居继续交互。如此,依照协议规定创建交易,并相互连接。 85 | 86 | 在上面的案例中,交易 `1` 和 `2` 是首先离线的一批。它们与在线 tangle(online tangle) 最后已知的 tip 相连。随后的交易与往常一样不断地附加到后面。一旦有向主 tangle(main tangle) 的提交(commit),离线的子 tangle 就会通过创建交易 `8` 得到最终确定,它会将离线 tangle 与当前在线 tangle 的 tip 进行合并。随后,交易 `8` 变为一个合法的 tip,并且可供后面的在线交易进行选择和验证。在线连接到交易 `8` 的下一个用户,将会在他们的验证路径上包含所有的离线交易。 87 | 88 | 请注意,正如上文,只有当离线交易跟其他交易一样,被加入到主 tangle 中,离线交易才会被完全确认。如果离线分支中的任何交易与主 tangle 冲突,交易 `1` 到 `8` 就不会被确认。再一次的,它可能会花费随后几个交易的时间,直至冲突对于主 tangle 的所有(或者大部分) tip 都可见(也就是上面所说的 “双花”)。 89 | -------------------------------------------------------------------------------- /content/misc/articles.md: -------------------------------------------------------------------------------- 1 | 2 | - [Fundamental challenges with public blockchains](https://medium.com/@preethikasireddy/fundamental-challenges-with-public-blockchains-253c800e9428) 3 | - [](https://hackernoon.com/ten-years-in-nobody-has-come-up-with-a-use-case-for-blockchain-ee98c180100) 4 | 5 | - [从分布式计算的角度看区块链](http://cs.brown.edu/courses/csci2952-a/papers/perspective.pdf) 6 | 7 | - [](https://medium.com/@gustav.simonsson/ethereum-probabilistic-micropayments-ae6e6cd85a06) 8 | -------------------------------------------------------------------------------- /content/misc/bitcoin-fork.md: -------------------------------------------------------------------------------- 1 | 为什么比特币会分叉 2 | ================== 3 | 4 | scaling debate 5 | 6 | 大区块 BCH 7 | 8 | Bitcoin Core: segwit 闪电网络 9 | 10 | 比特币是一个协议,分叉表示修改协议,或者说协议升级。 11 | 12 | 软分叉 13 | 硬分叉 14 | 15 | 无论是软分叉还是硬分叉,都可能会对协议造成根本性的改变。但是区别在于:软分叉是向后兼容的,新协议与旧的协议是兼容的,而硬分叉不向后兼容。 16 | 17 | 硬分叉是从旧协议的永久性分裂。 18 | 19 | 比特币分叉可以追随到 [2013 年 5 月](https://bitcoin.org/en/alert/2013-03-11-chain-fork),[同年 8 月](https://archive.is/oKLBa) 再次分叉。 20 | 21 | ![A Blockchain Fork](https://news.bitcoin.com/wp-content/uploads/2017/11/FORKMAS.jpg) 22 | 23 | 在 2013 年之后的分叉,主要是关于可扩展性问题的分歧,scaling 24 | 25 | 2010 年最初的区块大小限制为 1 MB 引起了不少[争议](https://bitcointalk.org/index.php?topic=1347.msg15366#msg15366) 26 | 27 | 中本聪实现了这个协议变化,[1 MB](https://sourceforge.net/p/bitcoin/code/103/tree//trunk/main.h?diff=515630145fcbc978e39dbaa5:102) 28 | 29 | 人们想要扩容, 因为网络越来越拥堵,区块已满,也导致交易费暴涨。 30 | 31 | 2015 年之前 每笔交易 0.01 美元 -> 5- 10 32 | 33 | 交易瓶颈导致手续费更高,才能够让交易更快确认。 34 | 35 | 也有很多[会议](https://imgur.com/JUnQcue),矿工,开发者,比特币社区企业,但是都没有达成共识。 36 | 37 | BCH 移除了 segwit 代码,将区块大小上调为 8 MB. BCH 认为 segwit 是不必要的软分叉,决定在 Segwit2X 矿工集成改变之前分叉。 38 | 39 | [块高度 494784](https://segwit2x.github.io/segwit2x-announce.html) 40 | 41 | [纽约共识](https://medium.com/@DCGco/bitcoin-scaling-agreement-at-consensus-2017-133521fe9a77) 42 | 43 | 1. 软分叉 segwit 44 | 45 | 2. 1 MB -> 2 MB 46 | 47 | 48 | ### 共识 49 | 50 | 51 | 钱包客户端,交易所支持 52 | 53 | 54 | 比特币分叉点: 55 | 56 | - 智能合约 57 | - 零知识证明 58 | - 跨链 59 | 60 | BCH 矿工 BTC 61 | 62 | Bitcoin God 63 | 64 | Super BTC 65 | 66 | [A Simple Guide to What Bitcoin Forks Are and Why They Happen](https://news.bitcoin.com/a-guide-to-what-a-bitcoin-fork-is-and-why-they-happen/) 67 | 68 | Ripple 银行和全球价值转移 2012 年发布 但是实际上 ripple 比 比特币更早 69 | 70 | Namecoin 是 bitcoin 的第一个分叉币 71 | 72 | 根据 [Bitcoinmagazine](https://bitcoinmagazine.com/articles/introducing-ripple/) 2014 年就有了 ripple 公司 73 | 74 | ripple 3.6s 确认,bitcoin 一个小时 75 | 76 | 寻求更大回报,从 bitcoin 转向 ripple. 77 | 78 | [What Is Ripple and Why Is It Beating Both Bitcoin and Litecoin?](http://fortune.com/2017/12/13/ripple-litecoin-bitcoin-price-2018/) 79 | 80 | [10 things you need to know about Ripple](https://www.coindesk.com/10-things-you-need-to-know-about-ripple/) 81 | -------------------------------------------------------------------------------- /content/misc/blockchain-consensus.md: -------------------------------------------------------------------------------- 1 | 区块链就技术层面而言,共识(consensus)是核心。 2 | 3 | 共识是为了防双花(double spending) 4 | 5 | 区块链中的共识主要有四类: 6 | 7 | - PBFT: Practical Byzantine Fault Tolerance,实用拜占庭容错 8 | - PoW: Proof of Work, 工作量证明 9 | - PoS: Proof of Stake, 权益证明 10 | - DPoS: Delegated Proof of Stake, 委托权益证明 11 | 12 | 13 | 除了区块链共识,现在还经常提到一个概念:DAG(Directed Acyclic Graph,有向无环图) 14 | 15 | ### PoW 16 | 17 | 代表:Bitcoin, Ethereum, Litecoin, Zcash, Dogecoin 18 | 19 | 难度最大的链 20 | 21 | 22 | ### PoS 23 | 24 | 25 | ### DPoS 26 | 27 | 28 | ### PoA 29 | 30 | 31 | 参考: 32 | 33 | [1] [浅谈区块链共识机制与分布式一致性算法](https://bitcointalk.org/index.php?topic=1543391.0) 34 | 35 | [2] [Concensus in Blockchain Systems. In Short.](https://medium.com/@chrshmmmr/consensus-in-blockchain-systems-in-short-691fc7d1fefe) 36 | 37 | [3] [](https://news.ycombinator.com/item?id=14021221) 38 | [4] [A Hitchhiker’s Guide to Consensus Algorithms](https://hackernoon.com/a-hitchhikers-guide-to-consensus-algorithms-d81aae3eb0e3) 39 | -------------------------------------------------------------------------------- /content/misc/dpos-consensus-algorithm-this-missing-white-paper.md: -------------------------------------------------------------------------------- 1 | # DPOS 共识算法 - 缺失的白皮书 2 | 3 | > 原文:https://steemit.com/dpos/@dantheman/dpos-consensus-algorithm-this-missing-white-paper 4 | > 5 | > 网络上已经有了好几个版本的译文,可能是原文写的没那么“平易近人”,这些译文我都看得不太懂:) 6 | > 7 | 8 | ![Delegated Proof of Stake](https://steemitimages.com/DQmRR65U9ZYyMTVqtxRV6vu7AXj6GEouURjpEaoeSkHeHM8/image.png) 9 | 10 | 这篇“缺失的白皮书”是对委托权益证明(Delegated Proof of Stake, DPOS)的分析,旨在分析 DPOS 的工作原理及其鲁棒性(robust)的根源。[DPOS 的早期描述可以在 bitshares.org 找到](https://bitshares.org/technology/delegated-proof-of-stake-consensus/);不过,那个描述里包含了很多与实际共识不大相关的内容。 11 | 12 | 本质上,所有区块链都是一种由交易驱动的确定性状态机。而共识,是就确定性交易顺序达成一致并过滤无效交易的过程。有各种不同的共识算法都可以产生等效的交易排序,但通过在多个区块链上长年累月的可靠运行,DPOS 已经证明其具备健壮性、安全性和有效性。 13 | 14 | 像所有共识算法一样,块生产者(俗话就是出块人)可能导致的最大损害是审查(censorship)。所有块的有效性必须基于确定性的开源状态机逻辑。 15 | 16 | ## DPOS 算法概要 17 | 18 | DPOS 算法分为两部分: 19 | 20 | 1. 选择一组块生产者 21 | 2. 调度生产 22 | 23 | 选择出块人的过程,确保了利益相关方(stakeholder,通俗点也可以说是持币人)最终能有控制权,因为当网络不能顺利运行时,利益相关方的损失最大。就实际运行中如何达成共识而言,如何选择出块人其实几乎没有多大影响,因此,本文将重点介绍在选好出块人之后,如何达成共识。 24 | 25 | 为了帮助解释这个算法,我将假设 3 个块生产者 A,B 和 C。因为需要达成 2/3+1 的多数共识来解决所有情况,这个简化的模型将假设生产者 C 是打破僵局的那个人(也就是 2/3+1 里面的 1,胜负手)。在现实世界中,将有 21 个或更多的出块人。像工作量证明一样,一般规则是最长链胜出。任何时候,当一个诚实的节点看到一个有效的更长链,它都会从当前链切换到更长的这条链。 26 | 27 | 我将举例说明在大多数能够想到的网络条件下,DPOS 是如何运行的。这些例子应该可以帮助您理解为什么 DPOS 稳健且难以破坏。 28 | 29 | ## 正常操作(Normal Operation) 30 | 31 | 在正常操作下,出块人轮流每 3 秒钟出一个块。假设没有人错过自己的轮次,那么将会产生最长链。出块人在被调度轮次之外的任何时间段出块都是无效的。也就是说,如果没有轮到自己出块,出的任何块都是无效的。 32 | 33 | ![normal operation](https://steemitimages.com/DQmULkjtBAq4d3QypAZecgV4mAVJ9VDw9FjhbWJe7kWqZK8/image.png) 34 | 35 | ## 少数分叉(Minority Fork) 36 | 37 | 如果出现不超过节点总数三分之一的恶意或故障节点,那么可能会产生少数分叉(minority fork, 或者可以说是小群体分叉)。在这种情况下,少数分叉每 9 秒只能产生一个块,而多数分叉每 9 秒可以产生两个块。这样,诚实的 2/3 多数将永远比少数(的链)更长。 38 | 39 | ![minority fork](https://steemitimages.com/DQmaXit43FxdSQrn7PshKtJcnTY5SYJb1vSArf26ys34NDF/image.png) 40 | 41 | ## 离散少数的双重生产(Double Production by Disconnected Minority) 42 | 43 | 如果有很多“各自为政”的小群体,那么他们可以试图产生无数的分叉,但是因为少数人在出块速度上注定比多数人更慢,所以所有小群体的分叉都会比多数人的那条链短。 44 | 45 | ![double production by disconnected minority](https://steemitimages.com/0x0/https://steemitimages.com/DQmXsgRLQhmTVxjte48va1Qe6QePVzXUtmXRS8pjGPzUqGg/image.png) 46 | 47 | ## 网络碎片化(Network Fragmentation) 48 | 49 | 网络完全有可能碎片化,从而导致没有任何分叉拥有数量上占多数的出块人。在这种情况下,最长链将倒向最大的那个小群体(“矮子里面挑高个”)。当网络连通性恢复时,其他较小的小群体会自然切换到最长的那条链(也就是最大的小群体那条链),继而恢复明确的共识。 50 | 51 | ![network Fragmentation](https://steemitimages.com/DQmb4UaGGR8nNW5pmtoDE7Z9pvMd1LnKSpdi3utHUWeyfQZ/image.png) 52 | 53 | 有可能存在这样三个分叉,其中两个较长的分叉长度相同。在这种情况下,当第 3 个(较小)分叉的出块人重新加入网络时,就会打破平局。出块人总数为奇数,因此不可能长时间保持平局。稍后我们还会谈到出块人“混洗(shuffle)”,它使得出块顺序随机化,从而确保即使是出块人数目相同的两个分叉,也会以不同的速度增长,最终导致一个分叉胜出。 54 | 55 | ## 在线少数的双重生产(Double Production by Connected Minority) 56 | 57 | 在这种场景下,少数节点 B 在其时间段内产生了两个或更多的竞争块(alternative block)。下一个出块人(C)可以选择基于 B 产生的任意一个块继续往前走。如此一来,C 的这条链就成为最长链,而所有选择 B1 的节点都将切换到最长链。即使少数不良出块人试图广播再多的竞争块也无关紧要,因为它们作为最长链的一部分永远不会超过一轮(round,所有出块人挨个出块,转完一圈就是一轮)。 58 | 59 | ![double production by connected minority](https://steemitimages.com/0x0/https://steemitimages.com/DQmXstNcMtg5H6o1a955LZNGwbiCaygQnWzK81yeR8Uirvz/image.png) 60 | 61 | ## 最后不可逆块(Last Irreversible Block) 62 | 63 | 在网络碎片化的情况下,在相当长的一段时间内,很有可能有多个分叉都持续不断地增长。长远来看,最长的链终将获胜,但观察者(observer)需要一种确切的手段,以此来确定一个块是否在增长最快的链上。这可以通过看是否有 2/3+1 多数出块人的确认来确定。 64 | 65 | 在下图中,块 B 已被 C 和 A 所确认,即表示有 2/3+1 多数确认,由此我们可以推断没有其它链会比这条链更长 – 如果 2/3 的出块人是诚实的。 66 | 67 | ![last inreversible block](https://steemitimages.com/DQmWjbpfju5vj1EGBtfLKSGcM4CpHmYppgB9cfev2dFLtyF/image.png) 68 | 69 | 请注意,这个“规则”类似于比特币的 6 个块确认。一些“聪明”人也许可以谋划一系列事件,使得两个节点的最后不可逆块不同。这种极端的例子要求攻击者能完全控制通信延迟,并且在几分钟内控制两次--而不仅仅是一次。即便这真的发生了,最长链胜出的长期规则仍然适用。我们认为这种攻击的可能性足够接近 0,且经济后果无关紧要,因此不足为虑。 70 | 71 | ## 出块人不足(Lack of Quorum of Producers) 72 | 73 | 有一种不太可能会出现的情况,即没有明确到底有多少个出块人,在这种情况下,少数人还是可以继续出块。在这些块里,利益相关方可以包含更改投票的交易。这些投票可以选出一组新的出块人,并将出块参与率恢复到 100%。一旦如此,这条小群体的链最终将超过所有其他出块参与率低于 100% 的链。 74 | 75 | 在此过程中,所有观察者都会知道,在出现一条参与率超过 67% 的链之前,网络都处于不稳定的状态。如果有人选择在此条件下进行交易,那么他将承受与接受不到 6 个确认类似的风险。他们也知道存在这样的小概率事件,即最终的共识(或者说最长链)出现在另一个不同的分叉上。在实际操作中,这种情况仍然要比接受少于 3 个比特币确认要安全的多。 76 | 77 | ## 多数出块人出现问题(Corruption of Majority of Producers) 78 | 79 | 如果多数出块人出现问题,那么他们可能产生无限的分叉,每个分叉都看起来以 2/3 多数确认向前推进。这种情况下,最后不可逆块算法蜕变为最长链算法。最长链就是为最多人认同的链,而这条链实际将由少数剩下的诚实节点决定。这种行为不会持续很长时间,因为利益相关方最终会投票替换掉这些出块人。 80 | 81 | ![corruption of majority of producers](https://steemitimages.com/0x0/https://steemitimages.com/DQmZvNhkNbLLmvFQZEDhiXrCkmdEHm4q5tmL5Jo7ngJFWJG/image.png) 82 | 83 | ## 交易权益证明(Transactions as Proof of Stake, TaPoS) 84 | 85 | 用户为一个交易签名,是基于对区块链状态的一个假设,而这个假设又基于他们对最近几个块的“认识”(perception)。如果最长链的共识发生改变,则很可能会使签名者之前签名时所依据的假设失效。 86 | 87 | 就 TaPoS 而言,所有交易都包含最近一个块的哈希,如果该块在区块链中不存在,那么认为这些交易是无效的。任何在孤儿分叉(orphaned fork)上给交易签名的人,最终都会发现该交易无效,且无法迁移到主分叉。 88 | 89 | 这个过程有个附带的好处,它可以抵御试图产生替代链的长程攻击(long-range attack)。每次交易时,利益相关方都直接对区块链做出确认。随着时间的推移,所有的块都是由所有利益相关方确认过的,这在一条伪造的链(forged chain)所做不到的。 90 | 91 | ## 确定性出块人混洗(Deterministic Producer Shuffling) 92 | 93 | 在上面我们所展示的所有案例中,出块人按循环调度出块。实际上,每出 N 个块(N 是出块人数量),出块人集合都会进行一次混洗。这种随机性确保了出块人 B 不会总是忽略出块人 A,并且当出现多个数量出块人相同的分叉时,最终会有一个分叉胜出。 94 | 95 | ## 结论 96 | 97 | 在每一个想得到的自然网络破坏下,DPOS 都是健壮的,甚至在面对大部分出块人作弊时,也是安全的。不像其它共识算法,当大多数出块人出现问题时,DPOS 仍然可以继续工作。在此过程中,社区可以投票替换掉不合格的出块人,直到恢复 100% 参与率。在如此高强度和变化多端的故障条件下,我还不知道有任何其它算法也可以依然保持如此健壮。 98 | 99 | DPOS 之所以能有这么高的安全性,归根结底来自于其选择出块人和验证节点质量的算法。通过赞成投票制(approval voting),可以确保即使一个人拥有 50% 的有效投票权,也不能独自选择一个出块人。DPOS 的设计初衷是在良好的网络连接,诚实节点 100% 参与共识的情况下优化性能,这使得 DPOS 有能力在平均只有 1.5 秒的时间内以 99.9% 的确定性确认交易,同时能够以一种优雅和可检测的方式降级 – 降级也不是什么大事。 100 | 101 | 其他共识算法的设计初衷是,节点不诚实且网络条件恶劣。这种设计的最终结果就是,网络的性能更差,延迟更高,更新开销大,并且在 33% 节点出现问题的情况下就会彻底停止正常运转。 102 | 103 | 在 BitShares 成功运行三年,Steem 运行一年期间,我们经历了各种各样的网络条件和软件错误。对于各种状况,DPOS 都成功地得以应对,并且证明了在维护共识的同时,处理了比任何其它区块链更多的交易。 104 | -------------------------------------------------------------------------------- /content/misc/how-the-bitocin-protocol-actually-works.md: -------------------------------------------------------------------------------- 1 | >原文:[How the Bitcoin protocol actually works](http://www.michaelnielsen.org/ddi/how-the-bitcoin-protocol-actually-works/) 2 | -------------------------------------------------------------------------------- /content/misc/on-public-and-private-blockchains.md: -------------------------------------------------------------------------------- 1 | 什么是公有链、私有链 2 | ==================== 3 | 4 | >原文: [On Public and Private Blockchains](https://blog.ethereum.org/2015/08/07/on-public-and-private-blockchains/) 5 | 6 | ## 公有链(Public blockchain) 7 | 8 | 任何人都可读,任何人都可以发送交易,任何人都可以参与**共识过程(consensus process)**。所谓共识,就是决定将哪个块加入到区块链,决定当前状态。与中心化或类中心化信任不同,公有链由密码学和经济学保障安全,即将经济激励和使用像 PoW 或 PoS 等机制的密码学验证组合到一起。对于共识过程有个基本原则,简单来说,就是谁有多少经济资源,谁就能对共识过程产生多大的影响。所谓经济资源,PoW 就是算力,PoS 就是代币。这样的区块链,就可以认为是“完全去中心化的”区块链。 9 | 10 | ## 联盟链(Consortium blockchain) 11 | 12 | 如果一个区块链能够参与共识的节点是预先选定的,那么就是联盟链。比如一个有着 15 个金融机构的联盟,每个机构维护一个节点,只有至少其中 10 个节点按照顺序签名过的块,才是有效区块。可能所有人都可以读取,也可能只有部分人有读取权限,也可以采用混合方式,比如利用一个开放的 API 进行有限次数的查询。这样的区块链,就可以认为是“部分去中心化的”区块链。 13 | 14 | ## 私有链(Private blockchain) 15 | 16 | 如果一个区块链的写入权限由一个组织完全控制,那么这就是私有链。私有链的读取权限可以是公开的,也可以施加任意程度的限制。由于像数据库管理,审计等等应用都是属于公司内部事务,所以很多时候,公开可读其实也并非必要。 17 | 18 | 私有链,准确来说,就是一个传统的中心化系统附加某种程度的密码学审计功能。 19 | 20 | 私有链与公有链,各有其长处与短处。 21 | 22 | 私有链的优势有: 23 | 24 | 1. 可以很容易地修改区块链的规则,回滚交易,修改余额等等。 25 | 2. 验证人已知,所以不会出现中国矿工共谋的 51% 攻击。 26 | -------------------------------------------------------------------------------- /content/misc/others/list.md: -------------------------------------------------------------------------------- 1 | Coin/Token | GitHub 2 | :----: | :----: 3 | Ripple | [ripple](https://github.com/ripple) 4 | -------------------------------------------------------------------------------- /content/misc/others/ouroboros.md: -------------------------------------------------------------------------------- 1 | self mining 2 | 3 | 1. 具有可证的安全性 4 | 2. 采用 Pos 而不是 PoW,并提出了一个新的奖励机制,在该机制下,良好的行为处于一个近似纳什均衡的状态,并且可以缓解自私挖矿等。 5 | 6 | PoW 的一大弊端是消耗资源。 7 | 8 | PoW 的关键作用在于解决了一种随机的“领导人选举(leader election)”问题,也就是选择一个矿工来出下一个块。 9 | 10 | 出块机会跟算力成正相关,而“自私挖矿”可能会破坏这种机制。 11 | 12 | 13 | 于是引入 PoS,与投入计算资源不同,PoS 投入的是 *stake*,通常也就是所拥有的代币(token),有多少权益,就有多大的机会出块。 14 | 15 | 之前的 PoS 的区块链协议,只是从实践中证明了可以抵抗某些类型的攻击,但是并没有严格的形式化证明。所谓启发式的(heuristic)PoS 已经早已存在,比如 NXT, Neucoin, Blackcoin, Tendermint, Bitshares. 这些协议都是有不足的。 16 | 17 | Proof-of-Work Proof-of-space proof-of-space-time 18 | 19 | PoS 的基本问题是模拟领导人选举过程。引入 entropy 熵 20 | 21 | “grinding” 漏洞 22 | 23 | 24 | 提出 ouroboros 可证的 PoS 系统 25 | 26 | 1. 一个模型,形式化 PoS 区块链协议。关注 persistence and liveness 27 | 28 | persistence:如果一个节点认为一笔交易稳定,那么其他正常节点也认为如此。该属性的确定性由一个安全参数 k 确定,比如 超过 k 个块高度。 29 | 30 | liveness: 一笔合法交易存在一定时间后,比如 u 时间,就会认为是 stable。 31 | 32 | 2. 提出一个新的区块链协议。 33 | 34 | 35 | 使得 PoS 独特之处在于,PoS 的权益随着时间变化,信任假设也会随着时间演进。 36 | 37 | 基本问题是 leader election 38 | PoW - randomized leader election -- invest computational resources 39 | 40 | PoS - *stake* proportionally -- coin 41 | 42 | 43 | stakeholder 同时赋予义务与权力 assign work as well as rewards 44 | 45 | 46 | inspiredy by the two properties, persistence and liveness 47 | 48 | 49 | First, provice a formalized model 50 | 51 | Second, provide a blockchain protocol. coin-flipping protocol produces the randomness of the leader election process. 52 | 53 | Third, guarantee the persistence and liveness. 54 | 55 | Forth, a novel incentive strcture of the protocol. Nash equilibrium. 56 | 57 | Fifth, a stake delegation algorithm. 58 | -------------------------------------------------------------------------------- /content/misc/the-blockchain-economy-a-beginners-guide-to-institutional-cryptoeconomics.md: -------------------------------------------------------------------------------- 1 | 区块链经济学:制度加密经济学入门指南 2 | ==================================== 3 | 4 | >原文:[The Blockchain Economy: A beginner’s guide to institutional cryptoeconomics](https://medium.com/@cryptoeconomics/the-blockchain-economy-a-beginners-guide-to-institutional-cryptoeconomics-64bf2f2beec4) 5 | > 6 | >译文:[区块链经济学:制度加密经济学入门指南](http://ethfans.org/posts/the-blockchain-economy-a-beginners-guide-to-institutional-cryptoeconomics) 7 | > 8 | >虽然已经有了译文,不过还是想自己再学习一下。 9 | > 10 | >以下内容是在译文的基础上,加上些许自己的理解整理而来,所以严格来说,算不上是翻译,而是一个笔记。 11 | > 12 | ><<天道>>里面说,透视社会依次有三个层次,技术,制度和文化,区块链属于制度层面。 13 | > 14 | 15 | 区块链是一个数字化,去中心化的分布式 **账本(ledger)**。但是这句话反过来说,是不正确的。也就是说,区块链是分布式账本,但不是每个分布式账本都是区块链。比如,在很多人眼里,私有链,联盟链,或者说许可链,都只是分布式账本,而不是区块链。区块链的核心是共识,是人人可参与。 16 | 17 | 说到区块链的重要性,很多人会从比特币,从货币历史谈起。但是,货币仅仅是区块链第一个成功的应用而已,并且不太可能是它最重要的一个案例。 18 | 19 | 把账本描述为一个革命性的技术似乎看起来很奇怪,毕竟现在看来,账本就是一行一行枯燥的记录而已,买入卖出,借入借出,谁有多少钱,谁欠谁多少钱,诸如此类。但是账本非常重要,正是由于账本的不可或缺,区块链才是一个必然的趋势。 20 | 21 | ## 账本溯源 22 | 23 | 账本无处不在。账本所做的不仅仅是记录账户之间的交易。简单来看,一个账本就是由一些规则所塑造的数据而已。但是,任何时候,只要我们想就 **事实(fact)** 达成 **共识(consensus)**,就会用到账本。现代经济的基石就是由账本所记录的事实。 24 | 25 | **账本确认了所有权(ownership)**。财产契据登记好比一张地图,它显示出谁拥有什么,以及他们的土地是否受到过警告或者破坏。Hernando de Soto 就[曾经证明](https://www.amazon.com/dp/B00CW0MA1S)过,当自己的财产没有在账本中得到确认时,那些穷人是何等的痛苦。公司实际上就是一个账本,就像是一个由所有权、雇佣和生产关系构成的网络,并且这些关系都有着同一个目的。社团也是一个账本,决定了谁可以受益,谁不能受益。 26 | 27 | **账本确认了身份(identity)**。商家只有在政府的账本上登记身份后,才能在纳税法前提下,最终他们的存在性和状态。出生证明、死亡证明和结婚证明,都是用来记录个体(individual)存在的关键时刻,而当个体与世界交互的时候,这些信息就可以用来确认个体身份。 28 | 29 | **账本确认了状态(status)**。公民身份是一个账本,记录着作为国家公民,哪些人应当享有权利和履行义务。选民名册(electroral roll)是一个账本,记录在册的选民才进行投票(澳大利亚是普遍选举制)。雇佣是一个账本,让雇员可以按照合约索偿()来获取工作报酬。 30 | 31 | **账本确认权威(authority)**。账本可以证明谁能够合法地在国会中占据席位,谁能接入相关的银行账户,谁能与儿童一起工作,以及谁能进入禁地。 32 | 33 | 本质上,账本将经济和社会关系进行了映射(map)。 34 | 35 | 人们对事实及其变化达成一致(即对账本内容达成共识,以及对账本的精确性充分信任),是市场资本主义的根基之一。 36 | 37 | ## 所有权,占有权和账本 38 | 39 | 在这里对所有权(ownership)和占有权(possession)的概念做一个区分。这一点很重要,却也很容易被忽视。 40 | 41 | 以护照为例。每个国家都主张其出入境管治的权利,每个国家都维护着一个账本,其中记录着哪些公民可以旅行。护照是一种实物,也可以称其为一种**令牌(token)**,用以查阅这个账本。 42 | 43 | 在数字时代之前,占有权指的是对那种权利的所有权。澳大利亚的护照账本,由各州政府持有的索引卡组成。当旅行人员向边境的工作人员出示护照时,工作人员就可以做出如下推测:这个旅行者被记录在一个远程账本上,并被获准旅行。当然,这种激进的边境管制,很容易遭受诈骗风险。 44 | 45 | ![一张收藏在澳大利亚国家档案馆的比利时护照,A435 1944 / 4 / 2579](../images/127313-9caab2339f93b153.png) 46 | 47 | 占有权**隐含(imply)**了所有权,但是所有权**不等于**占有权。如今,现代护照可以让当局直接确认所有权。基于其数字特征,航空公司和移民局可以通过访问国家护照数据中心,以此决定是否让这位旅客自由通行。 48 | 49 | 在区分所有权和占有权的案例中,护照是一种相对直接的例子。然而,比特币已经向我们展示了:**货币也是一种账本(money is a ledger, too)**。 50 | 51 | 银行票据凭证的占有权表明了所有权。在十九世纪,银行票据的持有者,即“持票人”,有权动用这些票据在发行银行对应的价值。这些银行票据作为发行银行的直接负债,并记录在银行的账本上。占有权表明所有权的制度表明:银行票据易受偷窃和伪造的影响。 52 | 53 | 在我们这个法币流通的时代,一张五美元的纸币(banknote)是无法退还给银行以换取黄金的。但其中的关系依旧存在 —— 纸币的价值取决于社会共识,这种共识有关于货币及其发行政府的稳定性。但不幸的是,正如津巴布韦、南斯拉夫和德国魏玛共和国意识到的那样,纸币并不是财富。一张钞票是对(人工合成的)记录在账本里的某种关系的调用(call),但如果那种关系瓦解了,那么钞票的价值也会随之消失。 54 | 55 | ## 账本的演变 56 | 57 | 虽然账本非常重要,但直到现在,账本技术几乎一直没有太大的变化。 58 | 59 | 账本出现在文字交流(written communication)的初期。在古代近东地区,**账本和书写**同时发展起来以记录生产,交易和债务。通过在泥板上刻下楔形文字,来详细记录口粮、税收、工人等详细信息,然后将其烧制保存。首个国际“社区”是以结构化的网络联盟形式组建的,[其运作方式和分布式账本(distributed ledger)非常相像](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3016649)。 60 | 61 | ![一张由大英博物馆收藏的巴比伦尼亚楔形文字账本残片,58278-](../images/127313-5e6c75f6cea7fa92.png) 62 | 63 | 账本的第一次重大变化出现在十四世纪,当时的人们发明了复式记账法(double entry bookkeeping)。通过同时记录借贷数据,复式记账法需要在多个(分布式)账本里保存数据,同时考虑各账本之间的一致性(reconciliation)。 64 | 65 | 到了十九世纪,随着大型公司和政府机构的兴起,账本技术有了新的发展。虽然这些**中心化账本(centralized ledger)**使得组织的规模和范围都急剧增加,但是却完全基于对中心化机构的**信任(trust)**。 66 | 67 | 到了二十世纪晚期,模拟化账本开始向**数字化账本(digital ledger)**转变。例如,在十九世纪七十年代,澳大利亚的护照账本就进行了数字化和中心化处理。数据库可以实现更加复杂的分配、计算、分析和跟踪。一个数据库是可计算且可搜索的。 68 | 69 | **但是,数据库依然是基于信任的。数字化账本的可靠性,依赖于维护其账本的组织及其雇佣的员工。区块链解决的正是这个问题。区块链是一个分布式账本,它无需依赖一个可信任的中心机构来维护和验证账本。** 70 | 71 | ## 区块链和资本主义经济制度 72 | 73 | 现代资本主义的经济结构逐步演变为用来服务这些账本。 74 | 75 | 2009 年诺贝尔经济学奖获得者 Oliver Williamson 提出:人们在市场、公司或者政府之间的生产和交换行为,取决于这些机构间的相对**交易成本(transaction cost)**。Williamson 交易成本的方式,指出了“什么(what)机构管理账本?”及“为什么(why)”的关键所在。 76 | 77 | 政府维护着权利(authority)、特权(privilege)、责任(responsibility)和准入(access)的账本。政府是可信赖的实体,他们保管着存有公民身份及其旅行权、纳税义务、社会保障权和财产所有权的数据库。当需要强制执行账本的时候,我们就会需要政府的帮助。 78 | 79 | 公司也同样需要维护账本。公司的专有账本涵盖了员工的职务和义务、所有权分配、人力及实物调度、供应商和客户以及知识产权和公司特权的管理。公司也经常被描述为是“一系列合约的联结(nexus of contracts)”。企业的价值,正是来自于这组联结的方式及结构。因此,公司实际上是一个合约和资本账本(a ledger of contracts and capital)。 80 | 81 | **公司和政府能够利用区块链技术,提高其工作效率及可信度**。跨国公司及其公司网络,需要对其全球范围内的交易进行核对,而区块链能够以近乎即时(near-instantaneously)地完成以上工作。政府可以利用区块链不可更改性(immutability),保证财产契据和身份记录是精确且未经篡改过的。区块链应用上设计良好的准入规则(well-designed permissioning rule),可以让公民和消费者更好地掌控他们自己的数据。 82 | 83 | **但是区块链同时也是反企业,反政府的**。区块链是一项制度级的技术,它是一种维护账本的新方式,[即协作性经济活动](https://www.academia.edu/33138299/Blockchains_and_the_economic_institutions_of_capitalism),这种方式与企业和政府完全不同。 84 | 85 | ![新资本主义经济制度](../images/127313-60b2cac4fb9fccfc.png) 86 | 87 | 区块链可以为企业所用,但是,区块链也可以将其取而代之。现在,一个合约和资本账本能够以去中心化和分布式的方式存在,这是一种前所未有的方式。无需政府支持,就可以维护和执行记载着身份、许可、特权以及授权的账本。 88 | 89 | ## 制度加密经济学 90 | 91 | 制度加密经济学(institutional cryptoeconomics)的研究对象是:密码学安全且无需信任的账本所带来的制度后果。 92 | 93 | 古典主义和新古典主义经济学家将稀有资源的生产和分配,以及支撑生产和分配的要素作为研究对象。 94 | 95 | 制度经济学是把将经济理解为一些规则(rule)。规则(如法律、语言、产权、规定、社会准则和意识形态)使得分散的和投机的人们之间的活动得以相互协作。规则可以促进交换——不仅是经济交换,同时还有社会和政治交换。 96 | 97 | 所谓加密经济学,正是聚焦在支撑区块链及其衍生应用上的经济学原理和理论。它着眼于区块链机制设计中主要使用到的博弈论和激励设计。 98 | 99 | 相反地,**制度加密经济学着眼于区块链和加密经济学中的制度经济学**。与制度经济学一样,经济是一个协调交换的系统。但制度经济学并非着眼于规则,而是聚焦在账本上:由规则构成的数据(data structured by rules)。 100 | 101 | 制度加密经济学对如下内容感兴趣: 102 | 103 | - 账本的治理规则; 104 | - 服务于这些账本的社会、政治和经济机构的发展; 105 | - 以及区块链的发明是如何在全社会范围内改变账本模式的。 106 | 107 | ## 经济学在区块链中的重要地位 108 | 109 | 制度加密经济学给了我们一种工具,用来理解区块链革命正在发生什么,以及我们无法预测的事情。 110 | 111 | 区块链是一项实验性的技术。它到底该用在哪里?这是大多数企业家的疑问。有些账本会被转移到区块上,而有些企业家虽然试图将账本也转移到区块链上,但是最后肯定会失败。区块链不是万能的。我们至今还未看到区块链杀手级的应用。我们现在无法预测账本、密码学、点对点网络的组合,在未来究竟会带来什么。 112 | 113 | **这一过程将极具破坏性**。我们预计,全球经济将长期面临不确定性,而这种不确定性来自于其背后的事实论据将有可能被调整、废除和重组。 114 | 115 | 区块链的最佳使用方式还有待“发现”。之后,必然会将其引入现实世界的政治和经济系统里,而这些系统里已经拥有一批为账本服务的资深且成熟的机构,因此,其代价必然存在。 116 | 117 | 账本的使用极其普遍,一些治理社会的基本原则唾手可得。因此,区块链的应用可谓是包罗万象的。 118 | 119 | ## 制度的创造性破坏 120 | 121 | 我们曾经也经历过这样的革命。 122 | 123 | 人们很容易将比特币和区块链的发明与互联网相提并论。区块链是互联网 2.0,亦或是互联网 4.0。互联网是一个强大的工具,它颠覆了我们的交互和商业模式。但任何这些比较都低估了区块链的价值。互联网可以让我们更加流畅、快速、有效地交流和交换。 124 | 125 | 然而,区块链让我们以不同的方式进行交换。对区块链技术的一个更好的比喻是机械时间的发明。 126 | 127 | 在机械时间出现以前,人类活动受制于大自然的临时调控:公鸡黎明报晓,夜晚逐渐黑暗。正如经济史学家 Douglas W. Allen 提出的,问题在于可变因素:“测量时间的变量太过繁多……以至于无法在许多日常活动中找到借鉴意义。” 128 | 129 | ![十二世纪的 Jayrun 水钟](../images/127313-f4f76e554a475825.png) 130 | 131 | “在任何地方都能感受到因减少度量时间的可变因素所带来的影响”,Allen如此写道。机械时间开启了一个让人难以置信且几乎不可能出现的经济组织的全新分类。机械时间使贸易和交换得以同步,超越了地域的限制。它让生产和运输可以相互协调,让一天的安排得以实现,让劳动按照工时得到相应的报酬(让劳动者知道他们是否得到了合理的报酬)。雇主和雇员都能根据一种标准化的,独立的工具来验证契约被履行了。 132 | 133 | ## 完全和非完全智能合约 134 | 135 | Oliver Williamson 和 Ronald Coase(1991年诺贝尔经济学奖获得者)将合约视为经济和商业组织的核心。合约也是制度加密经济学的核心,这正是区块链最具革命性的地方。 136 | 137 | 区块链上的智能合约,可以自动、匿名和安全地执行合同(contractual agreement)。智能合约可以取代当下一系列用以维护、实施和确认合同被执行的工作 -- 会计师、审计师、律师,以及法律系统的大部分工作。 138 | 139 | 但是,受到算法内容的限制,智能合约也有其局限性。经济学家聚焦在完备和不完备合约(complete and incomplete contract)的区别上。 140 | 141 | 完全合约明确了在任意意外事件下执行的条款。而不完全合同则允许在发生意外情况下,合同条款可以重新商榷。不完全合同对以下问题做了解释:为什么公司会进行交换?为什么市场上会出现交换?同时,它也为一系列关于公司的垂直整合和规模的问题提供了进一步指引。 142 | 143 | 完全合约是无法被执行的,而不完全合约比较昂贵。但通过智能合约,区块链可以降低与许多不完全合约相关的信息及交易成本,并且以此扩大可进行的经济活动的规模和范围。如此,市场得以在以前只有大公司才可以经营的地方运营,同时,商业和市场也得以在以前只有政府可以经营的地方运营。 144 | 145 | 具体在何时以何种方式实现上述情况,是企业家所面临的问题和挑战。当下,预言机将区块链算法世界和真实世界连接起来,让信任主体能够将信息转化为可以让智能合约处理的数据。 146 | 147 | 我们认为,区块链革命的真实收益应当来自于开发更加完善和更加强大的预言机——将不完全合约转化为可以在区块链上用算法编译及执行的充分完全的合约。 148 | 149 | 商业法庭的发展让中世纪的商业革命得以实现。可信任的高效预言机让贸易商可以私下执行协议。但对区块链而言,这场革命似乎还未到来。 150 | 151 | ## 政府将走向何方? 152 | 153 | 从税收,监管到服务交付,区块链经济给政务流程带来了全方位的压力。我们当下的研究项目就是对这一系列的变化进行调查。可以思考一下,例如,如何监管银行? 154 | 155 | 我们采用审慎监管(prudential control)制度来确保金融机构和公众互动是安全和稳固的。然而,我们常见的情况却是,这些监管(例如流动性和资本金要求)未能让储户和股东们观察到银行的账本。储户和股东们也无法惩罚公司和其管理层。 156 | 157 | 当储户们发现(或者只是单纯的想象)他们储蓄的银行可能无法兑现他们的存款,人们将争先恐后地取钱。 158 | 159 | ![电影《欢乐满人间》中的银行挤兑的场景(1964年)](../images/127313-db16fcaa52e1ecc9.png) 160 | 161 | 区块链可能的一种应用是,让储户和股东们持续监控银行的储备金和贷款,从而充分消除他们和银行管理层之间的信息不对称。 162 | 163 | 在这个世界上,市场自律是有可能实现的。因为相信区块链的不可更改性(immutability),所以人们确信不会出现意外,造成银行疯狂挤兑的情况。而监管机构的角色将受到限制,他们仅需保证区块链被正确及安全地构建。 164 | 165 | 一个更为深远的应用将会是**加密银行(cryptobank)** -- 个自治的区块链应用,可实现短借长贷,从而直接将借款人和贷款人进行匹配。一个由智能合约实现的加密银行,除了拥有和其他银行同等的透明性特征,还有一些完全无需监管介入的特性。例如,**加密银行拥有自行清偿的能力**。当加密银行在破产时,触发相关交易,将其资产将自动支付给股东和储户们。 166 | 167 | 在这样的世界里,政府应当承担何种监管角色尚不清晰。 168 | 169 | [Tyler Cowen 和 Alex Tabarrok 提出](https://www.cato-unbound.org/2015/04/06/alex-tabarrok-tyler-cowen/end-asymmetric-information):大多数政府监管,都是用来解决信息不对称问题。然而,在一个信息无处不在的世界里,这个问题已不复存在。区块链应用显著增加了信息流动,使其变得更为透明、永久和易获取。 170 | 171 | 在**“监管技术(regtech)”**方面,区块链也有用武之地 -- 将技术应用于审计、合规和市场监管等传统的监管职能。但不可否认的是,在区块链的世界里会出现一些新的经济学问题,它将要求我们制定出新的消费者保护和市场管理措施。 172 | 173 | 无论如何,针对类似银行这种基础经济形式的重建和重构,将不仅对如何实施监管,而且还将对监管应该做什么带来压力。 174 | 175 | ## 大企业将去向何方? 176 | 177 | 对于大企业,影响可能会非常深远。企业的经营规模,从通常是由覆盖其业务层次的成本驱动,转而由大规模金融投资下的不完全合约和技术必要性所驱动。这个商业模式意味着,股东资本主义是商业组织的主要形态。如果能够在区块链上开发更多的完全合约,则意味着企业家和创新者们能够同时维持他们的所有权、人力资源控制权和利润。虽然随着时间的变化,商业成功和金融资本之间的联系已经被不断削弱,但是现在,这种关系可能不仅仅是被削弱,而是破裂。**人力资本主义(human captialism)的时代正是黎明之时**。 178 | 179 | 企业家将会开发一个有价值的应用,并将其发布到在“野外(wild)”的,任何一个能够利用或者需要它的人都能够获得。而企业家只需观察他们钱包里累计的微支付即可。一个设计师可以将他的作品发布到“野外”,然后,终端消费者可以下载这个设计到 3D 打印机,从而马上获得相关产品。这种商业模式能使得澳大利亚拥有比目前更多的(本土化的)制造业。 180 | 181 | 这种让消费者直接与制造商或者设计师沟通的能力,将会限制中间人在经济中的作用。虽然物流公司仍会继续发展,但是,无人驾驶运输的出现也将颠覆此行业。 182 | 183 | 这里需要记住的是,任何商业模式的颠覆都会破坏公司的税基。或许政府将很难对商业活动收税,因此,我们可能会看到销售(消费)税和人头税将面临巨大压力。 184 | 185 | ## 结论 186 | 187 | 区块链及其相关的技术变革,将会大范围瓦解当代经济状况。工业革命引领了一个世界,在这个世界里商业模型可以通过等级和金融资本进行预测。而区块链革命,将会见证一个由人力资本主义和高度自治为主导的新经济模型。 188 | 189 | 这一切最终将如何呈现,目前尚不清晰,不过企业家和创新者将会一如既往地通过不断试错去解决这些不确定性。毋庸置疑的是,在我们确切知道这种颠覆将如何呈现之前,将会创造和毁灭大量的财富。 190 | 191 | 而我们的贡献在于,当这场颠覆出现时,提供一个模型,让人们清晰地认识到这场颠覆的含义。 192 | -------------------------------------------------------------------------------- /content/monero/what-is-monero.md: -------------------------------------------------------------------------------- 1 | 什么是门罗币?终极入门指南 2 | ========================== 3 | 4 | >原文: [What is Monero? The Ultimate Beginners Guide](https://blockgeeks.com/guides/monero/) 5 | 6 | 根据 [Monero(门罗) 官网](https://getmonero.org/): Monero 是一个安全,隐私和不可追踪的加密货币。通过使用密码学中一种特殊的方法,门罗确保了所有交易保持 100% 的不可关联和不可追溯性(unlinkable and untraceable)。在一个日益透明的世界,你会明白为什么门罗会被人们所期待。通过本文,我们将会看到门罗背后的机制,到底是什么使它如此特别。 7 | 8 | ## 起源 9 | 10 | 2012 年 7 月,Bytecoin ,CryptoNote 的第一个实现终于问世。CryptoNote 是一个应用层协议,它支撑了各种去中心化货币。尽管在很多方面,它跟比特币上的应用层很相似,但是两者仍有很多不同的地方。 11 | 12 | 尽管 bytecoin 十分有前景,但是人们也注意到发生了很多负面的事情,并且鉴于它已经产出了 80% 的币。所以,决定将 bytocin 分叉,新链上的币叫做 Bitmonero,最终被重新命名为 Monero(门罗),在世界语(Esperanto)中叫做“coin”,硬币的意思。门罗的出块时间为两分钟。 13 | 14 | 译者注:就在翻译本文之时,[bytecoin](https://bytecoin.org/) 似乎在 GitHub 上有了一个新账号 [bcndev/bytecoin](https://github.com/bcndev/bytecoin),这个账号加入 GitHub 的时间为 2018 年 2 月 6 号。原始的 repo 应该是 [amjuarez/bytecoin](https://github.com/amjuarez/bytecoin),但好像一直都不太活跃,star 几百,fork 几千,也是“器宇不凡”。而 [cryptonote](https://cryptonote.org/) 的 repo 为 [cryptonotefoundation/cryptonote](https://github.com/cryptonotefoundation/cryptonote),也是 fork 自 amjuarez/bytecoin。 15 | 16 | 门罗由一个 7 人的开发者团队领导,其中 5 人匿名,另 2 人已公开。他们是 David Latapie 和 Riccardo Spagni aka “Fluffypony”。项目是开源众筹的形式进行。 17 | 18 | ![The core team](https://blockgeeks.com/wp-content/uploads/2017/09/image12.jpg) 19 | 20 | ## 门罗的特别之处 21 | 22 | 那么,到底是什么使得门罗这么热门?CrytoNote 算法给了它什么独特之处?让我们来看一下。 23 | 24 | ### 属性 #1: 你的钱就是你的 25 | 26 | 你对交易有着绝对的控制权。你为你的钱负责。因为你的身份是私有的(private),没有人能够看到你把钱花到了哪儿。 27 | 28 | ### 属性 #2: 可替代性 29 | 30 | 多亏了隐私性,另一个有趣的属性是它是真的可替换的(fungible)。什么是可替代性(fungibility)?Investopedia 是这么定义的: 31 | 32 | >“Fungibility is a good or asset’s interchangeability with other individual goods or assets of the same type.” 33 | >所谓可替换性,指的是一个商品或资产与其他同类型个体商品或资产的互换性。 34 | 35 | 那么,什么是可替代的,什么又是不可替代的。 36 | 37 | 假设你从一个朋友那儿借了 20 美元。如果你还给他另外 20 美元的钞票,那么没问题。实际上,你甚至可以还给他一个 10 美元和 2 个 5 美元的钞票。仍然没问题。美元有着可替代的属性(但并非总是如此)。 38 | 39 | 但是,如果打算周末借某人的车,回来以后还给他另外一辆车,那么他很可能会揍你。实际上,如果你借走一辆红色宝马,然后还给他另一辆红色宝马,显然这并不可行。在这个例子中,车并不是一个可替代的资产。 40 | 41 | ## 加密货币中理想的可替代性是什么? 42 | 43 | 以 [bitcoin](https://blockgeeks.com/guides/what-is-bitcoin-a-step-by-step-guide/) 为例,它引以为豪的一点就是比特币是开放的账本,但是,这也意味着每个人都可以看到里面的每一笔交易,更重要的是,每个人都可以看到交易的踪迹。简单来说,如果你拥有一个曾经用于某个非法交易的比特币,比如购买毒品,那么它的交易细节里面将会永远有这样的印记。实际上,这“污染(taint)”了你的比特币。 44 | 45 | 在某些比特币服务提供商和交易所中,这些“被污染”的币与“干净的”币永远都不会被一视同仁。这就泯灭了可替换性(fungibility),这也是比特币经常为人所诟病的一点。毕竟,为什么别人做了错事,需要你来买单呢? 46 | 47 | 于是门罗诞生了。由于所有数据和交易都是不公开的,没有人能够知道你的门罗币在之前经历了哪些交易,也无法知道你的门罗币会用来购买什么。既然交易历史不会有人知道,自然也就不存在“交易”踪迹。因此,“被污染”的门罗币和“干净的”的门罗币也就不复存在,所以它们是可替换的! 48 | 49 | ### 属性 #3: 动态扩展性 50 | 51 | 比特币的扩展性问题由来已久。简单来说,比特币协议限定了区块大小为 1 Mb(译者注:扩容,BCH 等为后话)。在早期,比特币并没有任何区块大小的限制,但是后来为了防止垃圾交易,就施加了大小限制。 52 | 53 | 对于区块大小,门罗并没有任何“预先设定”的限制。这同时也意味着恶意矿工可能会通过超大区块堵塞系统。为了防止发生这种情况,系统有一个区块奖励的惩罚(block reward penalty)。工作方式如下: 54 | 55 | 首先,最后 100 个区块大小的中位数叫做 M100。假设矿工挖出了一个新的块,大小记为 NBS(New Block Size)。如果 NBS > M100,那么区块奖励会随着 NBS 超过 M100 的平方递减。 56 | 57 | 也就是说,如果 NBS 大于 M100 [10%, 50%, 80%, 100%],那么区块奖励随之减少 [1%, 25%, 64%, 100%]. 通常来说,区块大小超过 2 倍的 M100 是不被允许的,同时如果区块小于等于 60kb 则会免于任何的区块奖励惩罚。 58 | 59 | ### 属性 #4: 防 ASIC 60 | 61 | 在正式开始之前,让我们来明确一点。实际上,门罗不算是严格的“ASIC resistant”,但是制作针对门罗的 ASIC 将会成本高昂,以至于不值得如此操作。为什么呢?记住,当我们说门罗基于 CryptoNote 系统时,已经使得它与比特币截然不同。在基于 CtryptoNote 的系统中所用的哈希算法叫做 "CryptoNight"。 62 | 63 | 创造 Cryptonight 是为了构建一个更加公平,更加去中心化的货币系统。利用 cryptonight 的加密货币无法用 ASIC 挖矿。它的目的是希望可以杜绝出现矿池的出现,并使得货币分散地更均匀。 64 | 65 | 那么,是什么使得 CryptoNight 防 ASIC?(以下内容来自[monero.stackexchange.com](monero.stackexchange.com) 用户 user36303 的回答。) 66 | 67 | - Crytponight 需要 2 MB 的快速内存来工作。这意味着并行哈希会被一个芯片可以分配多少内存限制,同时保持尽可能地低成本,以免“入不敷出”。2 MB 的内存要比 SHA256 电路要耗费多得多的硅。 68 | 69 | - Cryptonight 是 CPU 和 GPU 友好型,因为它利用了 AES-Ni 指令集。基本上,如果你用的是没那么老的机器,由 Cryptonight 所完成的一些工作已经在硬件层完成。 70 | 71 | - 已经有不少说法说,想要把门罗从工作量证明算法切换至“Cuckoo Cycle”(一种不同于工作量证明的哈希)。如果这种切换真的发生,那么在 R&D 门罗 ASIC 友好型所耗费的所有工作都将付之东流。 72 | 73 | ### 属性 #5: 多密钥 74 | 75 | 门罗最令人费解的一个地方就是它的多重密钥(multiple keys)。在比特币和以太坊等等,你只有一个公钥和一个私钥。但是,在门罗这样的系统里,远不止此。 76 | 77 | **view key:门罗有一个 public view key 和 private view key。** 78 | 79 | - public view key 用于生成一次性的 stealth public address(隐匿的公开地址),资金将会通过这个地址发送给接收者(后面有解释)。 80 | 81 | - private view key 用于接收者扫描区块链来找到发送给他们的资金。 82 | 83 | 这是整个过程的概述。 84 | 85 | public view key 构成门罗地址的第一部分。 86 | 87 | **spend key: 如果 view key 大多是为了交易接收方,那么 spend key 就是全部有关于发送方。跟上面一样,有两个 spend key:public spend key 和 private spend key。** 88 | 89 | - public spend key 帮助发送方参与环交易(ring transaction),并验证密钥镜像(key image)的签名(后文有介绍) 90 | 91 | - private spend key 帮助创建密钥镜像,密钥镜像能够使得他们能够发送交易。 92 | 93 | public spend key 构成门罗地址第二部分。 94 | 95 | 门罗地址是一个 95 个字符的字符串,分别由 public spend key 和 public view key 构成。 96 | 97 | 现在可能会有点令人疑惑,不过先记住就好了,这些概念在接下来的几个章节会越来越清晰。 98 | 99 | 门罗涉及了哪些密码学相关的知识? 100 | 101 | ## 加密货币交易的工作方式是怎样的? 102 | 103 | 每笔交易都有两边,一边是输入,一边是输出。假设 Alice 需要给 Bob 发送一些比特币,交易看起来是怎样的? 104 | 105 | ### 交易输入 106 | 107 | 为了发起交易,Alice 需要花费从之前各种交易收到的比特币。记住,我们之前说过,每个币都来源于之前的交易。所以,Alice 可以将之前交易的输出作为新交易的输入。后面,当我们谈到“输出”时,尤其是在环签名(ring signature)一节,我们指的是将旧交易的输出成为新交易的输入。 108 | 109 | 所以,假设 Alice 需要从下列交易从获得输入,比如 TX(0), TX(1) 和 TX(2)。这三笔交易会被一起包含到这笔交易,并有一个交易输出 TX(Input). 110 | 111 | ![transaction input](https://blockgeeks.com/wp-content/uploads/2017/09/moner1.png) 112 | 113 | 这是从输入来看,下面让我们来看一下输出。 114 | 115 | ### 交易输出 116 | 117 | 输出就是 Bob 可以在之后交易花费的钱,也可能会出现找零,找零会返回给 Alice。找零会成为 Alice 未来任意交易的输入。 118 | 119 | ![transaction output](https://blockgeeks.com/wp-content/uploads/2017/09/monero2.png) 120 | 121 | 这笔交易非常简单,因为只有一个输出(除了找零)。其实交易很可能会有多个输出。 122 | 123 | 有了公钥加密以后,比特币交易才成为可能。为了对它有一个基本的理解,请看下图: 124 | 125 | ![transaction flowchart](https://blockgeeks.com/wp-content/uploads/2017/09/image14-1.png) 126 | 127 | 比特币用户首先选择私钥,公钥由私钥衍生而来。将公钥进行哈希得到一个公开的地址公布出去。如果 Alice 要给 Bob 发送 BTC,Alice 直接给 Bob 公开的地址发送即可。 128 | 129 | 那么,问题来了。这个地址是公开的!任何人都可以知道这个地址属于谁,继而知道他整个的交易历史,同时知道他有多少比特币。尽管比特币作为一个去中心化的加密货币非常成功,但是却不是一个好的隐私货币系统。 130 | 131 | 下图是门罗团队给出的“电子现金三角(Electronic cash triangle)” 132 | 133 | ![Electronic cash triangle](https://blockgeeks.com/wp-content/uploads/2017/09/image13-1.png) 134 | 135 | 正如他们所说,一个理想的电子现金应该满足三个前提: 136 | 137 | - 电子的 138 | - 去中心化的 139 | - 隐私的 140 | 141 | 于门罗,他们会尝试满足这三个标准。门罗背后的哲学就是完全隐私和不透明性。 142 | 143 | 发送方隐私由环签名(Ring Signature)实现。 144 | 145 | - Ring Signatures(环签名)保证了发送方隐私 146 | - Condidential Address(隐匿地址)保证了接收方隐私 147 | - Ring CT(Ring Confidential Transaction, 环隐匿交易)保证了交易隐私 148 | 149 | ## 门罗密码学 #1: Ring Signatures 150 | 151 | 为了理解环签名是什么,它是如何保护了发送者隐私。让我们从现实生活中的一个案例谈起,当你要给某个人发送支票时,是不要需要签名?但是,也正因如此,看到这个支票的任何人(和知道你的签名是什么样的人)就会知道你就是那个发送人。 152 | 153 | 现在,想一下。 154 | 155 | 假设,你从街上随机选择 4 个人。把你的签名和这四个人进行混合得到一个独一无二的签名。这样就没有人能够发现这是否真的是你的签名。 156 | 157 | 这就是环签名本质上的工作方式。让我们来看一下门罗里面这个机制到底是怎样的。 158 | 159 | 译者注:环签名和混币(coinjoin)差不多,区别在于混币需要信任第三方。更多内容可见:[What are the technical advantages of Ring Signatures (CryptoNote) compared to CoinJoin?](https://monero.stackexchange.com/questions/24/what-are-the-technical-advantages-of-ring-signatures-cryptonote-compared-to-co/81#81) 160 | 161 | 假设,Alice 发送 1000 XMR(XMR 即门罗币) 给 Bob,系统会如何使用环签名来隐藏她的身份? 162 | 163 | 首先,Alice 会确认她的“ring size(环大小)”。ring size 是取自区块链的随机输出,它等于 Alice 的输出值,即 1000 XMR。ring size 越大,交易越大,继而交易费越高。然后,她用 private spend key 对输出进行签名,并发给到区块链。另一点要注意的是,Alice 不需要向之前交易的所有者发送请求来使用这些输出。 164 | 165 | 那么,假设 Alice 选择的 ring size 为 5 ,也就是说 4 个 decoy output(诱骗输出) 和它自己的交易,从外面看起来就像这样: 166 | 167 | ![5 decoy ouput and alice's own transaction](https://blockgeeks.com/wp-content/uploads/2017/09/image16-1.png) 168 | 169 | 在一个环签名交易中,任意一个 decoy 就像真实输出一样,因为任何不相关的第三方(包括矿工)都无法知道发送方是谁。 170 | 171 | 那么,问题来了。 172 | 173 | 矿工要做的一个重要的事情就是防止“双花”。双花就是指在同一时间,同一笔钱出现在两笔,甚至更多的交易中。双花被矿工所解决。在一个区块链中,只有当矿工将交易包含在区块并出块,交易才算完成。 174 | 175 | 那么,假设 A 打算给 B 发送一个比特币,然后它发送同样一个币给 C,矿工会把其中一笔交易放到块里,并在处理过程中覆盖另一笔交易,防止双花。但是,这只有在矿工能够看到交易输入是什么,发送方是谁的时候才行得通。但是在门罗中,由于环签名这些都是不可见的。那么要如何防止双花呢? 176 | 177 | 答案就在更为精巧的密码学中。 178 | 179 | 门罗的每一笔交易都已它自己的唯一的密钥镜像(key image)(我们将会在后面看到密钥镜像背后的数学)。鉴于密钥镜像对于每个交易都是不同的,矿工就可以非常容易地检测,判断是否双花。 180 | 181 | 这就是门罗通过环签名实现发送方隐私的方式。接下来,我们会看到门罗如何通过 stealth address(隐匿地址) 保护接收方身份。 182 | 183 | ## 门罗密码学 #2: stealth address 184 | 185 | 门罗的最大一个卖点就是交易的不可关联性(unlinkability)。基本上,如果有人发送给你 200 XMR,应该没有人知道这笔钱是发送给你的。如果 Alice 要给 Bob 发送门罗币,除了 Alice,应该没人任何人知道 Bob 就是这笔钱的接收者。 186 | 187 | 那么,门罗要如何保证 Bob 的隐私? 188 | 189 | 记住,Bob 有两个 public key:public view key 和 public send key。为了推进交易,Alice 的钱包会用 Bob 的 public view key 和 public send key 来生成一次性独一无二的 public key。 190 | 191 | 下面是一次性 public key (P) 的计算方式: 192 | 193 | $$ 194 | P = H(rA)G + B 195 | $$ 196 | 197 | 其中: 198 | 199 | - r = Random scalar chosen by Alice. Alice 选取的一个随机的标量 200 | - A = Bob’s public view key. Bob 的 public view key 201 | - G = Cryptographic constant. 密码学常数 202 | - B = Bob’s public spend key. Bob 的 public spend key 203 | - H() = The Keccak hashing algorithm used by Monero. 门罗所使用的 Keccak 哈希算法 204 | 205 | 由这种方法生成一次性公钥,然后再生成在区块链里一次性的公开地址,这样的地址就叫做“stealth address”,Alice 就通过它给 Bob 发送门罗币。现在,Bob 要如何从数据的随机分布中解锁收到的门罗币呢? 206 | 207 | 还记不记得 Bob 也有一个 private spend key? 208 | 209 | 这是该它登场了。private spend key 就是用来帮助 Bob 扫描区块链找到他的相关交易。当 Bob 找到这笔交易,他可以通过一个 private key 取回他的门罗币,这个 private key 与一次性的 public key 相关。因此 Alice 付给 Bob 门罗币,无人知晓。 210 | 211 | 在继续之前,让我们来回过头看一下 key image. 如何计算 key image(I)? 212 | 213 | 我们已经知道了如何计算 one-time public key(P),我们也有了发送方的 private spend key,比如叫做 “x” 214 | 215 | $$ 216 | I = xH(P). 217 | $$ 218 | 219 | 从 key image "I" 计算出一次性的 public address P 十分困难(这是密码学哈希函数的一个属性,正着算很容易,反推很难),因此 Alice 的身份永远也不会暴露。 220 | 221 | 当 P 被哈希的时候,永远都会返回同一个值,意味着 H(P) 也总是同一个值。既然 x 的值对于 Alice 来说是个常数,她也就是永远也无法生成多个 I 值。这使得 key image 对于每一笔交易都是不同的。 222 | 223 | ### 门罗密码学 #3: Ring Confidential Transactions 224 | 225 | 所以,我们已经看到消费门罗的人是如何保持匿名性,我们也看到了接收者也保持了匿名。那么交易本身呢?是否有某种方式能够隐藏交易额本身? 226 | 227 | 在 Ring CT 实现之前,过去的交易大概是这样: 228 | 229 | 如果 Alice 要发送给 Bob 12.5 XMR,输出将会被分为 3 笔交易,10,2,0.5. 这些交易的每一笔都会有自己的环签名,然后被加入到区块链: 230 | 231 | ![monero](https://blockgeeks.com/wp-content/uploads/2017/09/image15-1.png) 232 | 233 | 尽管这确实保证了发送方的隐私,但是也将交易暴露给了所有人。 234 | 235 | 为了解决这个问题,基于 Gergory Maxwell 的研究实现了 Ring CT。Ring CT 其实很简单,它在链上隐藏了交易的数额。这也意味着所有的交易输入都不需要再被拆分为已知的面额,钱包现在可以从任意的 Ring CT 输出中选择 ring 成员。 236 | 237 | 译者注:Ring CT 论文:[RING CONFIDENTIAL TRANSACTIONS](https://eprint.iacr.org/2015/1098.pdf)。环形加密技术的基础仍旧是与比特币一样的基于Hash值的公钥+私钥加解机制。只是比特币是用接受者的公钥加密,接受者用与之配对的私钥解密验证。而环形加密则使用了多个公钥进行加密,并用接受者的私钥进行解密验证。见[这个答案](https://www.zhihu.com/question/60058310/answer/222755086)。 238 | 239 | 思考一下这是如何有助于交易隐私的? 240 | 241 | 既然有这么多的选择挑选 ring ,并且价值未知,也就不可能再看出这到底是哪一笔交易。 242 | 243 | 这 3 个因素可以一起来创建一个提供完全隐私的系统。但是对于门罗开发者来说,这还不够。他们需要另外一层安全保障。 244 | 245 | ### Kovri and I2P 246 | 247 | I2P 是一个路由系统,它能够让应用秘密地互相发送信息而无须任何外部干涉。Kovri 是 I2P 的 C++ 实现,它也会被集成到门罗里面。 248 | 249 | 如果你正在使用门罗,Kovri 将会隐藏你的网络流量,如此一来,被动的网络监控就根本不会暴露你正在使用门罗。为此,你的所有门罗流量将会被加密并通过 I2P 节点路由。节点就像瞎的看门人,它们会知道你的信息通过,但是不知道这些去向哪儿以及信息的具体内容。 250 | 251 | I2P 和门罗将会很好地共生,因为: 252 | 253 | - 门罗将会多一层防护层 254 | 255 | - I2P 所使用的节点数将会大幅度地提高 post 实现。 256 | 257 | Kovri 仍处于开发阶段(截止成文之时),尚未实现。 258 | 259 | ### 门罗价值和市值 260 | 261 | 门罗市值已经获得了巨大增长: 262 | 263 | ![monero's growth graph](https://blockgeeks.com/wp-content/uploads/2017/09/image18.png) 264 | 265 | 根据 coinmarketcap.com,截止 2018 年 2 月 16 日 17 时 41 分,流通的门罗有 15,726,996 XMR,每个门罗币价值 1,855.38 CNY,市值 29,179,631,714 CNY,排名 13。 266 | 267 | 门罗总量为 1840 万,挖矿奖励会持续到 2022 年 5 月 31。之后,系统设定为 0.3 XMR/min 的奖励。这是为了矿工能过持续的激励挖矿,而不仅仅依赖于交易费,毕竟门罗已经被挖完了。 268 | 269 | ## 如何存储门罗? 270 | 271 | 存储门罗最简单的方式是去 [mymonero.com](https://mymonero.com/)。 272 | 273 | Step 1: 点击 “Create a new account” 274 | 275 | ![点击 Create an Account](https://blockgeeks.com/wp-content/uploads/2017/09/image17-1.png) 276 | 277 | Step 2: 将 private login key 记下来 278 | 279 | ![记住登录私钥](https://blockgeeks.com/wp-content/uploads/2017/09/image22-1.png) 280 | 281 | Step 3: 输入登录私钥并获得地址 282 | 283 | ![地址](https://blockgeeks.com/wp-content/uploads/2017/09/image20.png) 284 | 285 | 完成! 286 | 287 | 很简单,是不是? 288 | 289 | 唯一要注意的是,不要泄露你的私钥。 290 | 291 | 如果忘记了私钥,点击 Account,然后点击 "Review Login Key"。 292 | 293 | ![Review Login Key](https://blockgeeks.com/wp-content/uploads/2017/09/image25.png) 294 | 295 | 然后就看到私钥: 296 | 297 | ![review the private login key](https://blockgeeks.com/wp-content/uploads/2017/09/image23-1.png) 298 | 299 | So easy! 300 | 301 | ## 门罗 Vs 比特币 302 | 303 | 那么,相互比较是难以避免的,让我们来看一下它们是如何堆叠的。 304 | 305 | 比特币引以为豪的是其开放透明性。区块链是一个公开账本,任何人可以在任何地方获取它,并查看过去所有的交易。比特币也相对易于获取和使用。 306 | 307 | [Lindia Xie](https://medium.com/@linda.xie) 在他的 medium 文章中,已经就门罗和比特币给出了一个很好的比较: 308 | 309 | ![comparison between Monero and Bitcoin](https://blockgeeks.com/wp-content/uploads/2017/09/image24-1.png) 310 | 311 | 上图的 makerket cap 已过时,可在 [coinmarketcap.com](http://coinmarketcap.com/) 查看最新市值。 312 | 313 | ### 门罗的优势与劣势 314 | 315 | 优势: 316 | 317 | - 隐私性最好的几个加密货币之一 318 | - 交易之间不可联系 319 | - 交易不可跟踪 320 | - 区块链没有区块限制,并且可动态扩展 321 | - 即使当门罗的供应耗尽,也会有 0.3 XMR/min 的供应量激励矿工 322 | - 经济上已经获得了巨大增长 323 | - 其透明性实可选的。如果有人想要交易对某些人可见,比如给审计人员查看密钥。这也使得门罗是可审计的加密货币。 324 | - 有一个非常有能力的强大开发团队领导工作 325 | 326 | 劣势: 327 | 328 | - 尽管门罗已经被设计为防 ASIC 来避免中心化,但是门罗接近 43% 的算力仍然为 3 个矿池所有: 329 | 330 | ![Monero Hash](https://blockgeeks.com/wp-content/uploads/2017/09/image27-1.png) 331 | 332 | - 比起其他加密货币,由于涉及了很多的加密操作,门罗的交易大小要大得多。 333 | - 门罗的钱包兼容性不强。事实上,门罗至今没有硬件钱包(截止成文之时)。 334 | - 入门有难度,并且尚未被广泛接纳。 335 | - 因为它并非是基于比特币的货币,门罗面临的问题是向其中加入一些元素相对更困难。 336 | 337 | ## 门罗的未来 338 | 339 | 毫无疑问,未来会更加开放和去中心化,门罗也会因其隐私性而越具吸引力。特别有趣之处在于,它是少数几个不是基于比特币的币,却是同时有着真正价值的“潜力股”。对门罗来说,随着它已经经历了惊人的增长,未来依旧光明一片。当实现 Kovri 以后,相信一切会变得更加有趣。 340 | -------------------------------------------------------------------------------- /content/orchid/orchidprotocol-whitepaper.md: -------------------------------------------------------------------------------- 1 | 兰花协议 2 | ======== 3 | 4 | >白皮书:[Orchid: Enabling Decentralized Network Formation and Probabilistic Micro-Payments](https://orchidprotocol.com/whitepaper.pdf) 5 | -------------------------------------------------------------------------------- /content/part-1/basic-prototype.md: -------------------------------------------------------------------------------- 1 | 基本原型 2 | ======== 3 | 4 | ## 引言 5 | 6 | 区块链是 21 世纪最具革命性的技术之一,它仍然处于不断成长的阶段,而且还有很多潜力尚未显现。 本质上,区块链只是一个分布式数据库而已。 不过,使它独一无二的是,区块链是一个**公开**的数据库,而不是一个私人数据库,也就是说,每个使用它的人都有一个完整或部分的副本。 只有经过其他“数据库管理员”的同意,才能向数据库中添加新的记录。 此外,也正是由于区块链,才使得加密货币和智能合约成为现实。 7 | 8 | 在本系列文章中,我们将实现一个简化版的区块链,并基于它来构建一个简化版的加密货币。 9 | 10 | ## 区块 11 | 12 | 首先从 “区块” 谈起。在区块链中,真正存储有效信息的是区块(block)。而在比特币中,真正有价值的信息就是交易(transaction)。实际上,交易信息是所有加密货币的价值所在。除此以外,区块还包含了一些技术实现的相关信息,比如版本,当前时间戳和前一个区块的哈希。 13 | 14 | 不过,我们要实现的是一个简化版的区块链,而不是一个像比特币技术规范所描述那样成熟完备的区块链。所以在我们目前的实现中,区块仅包含了部分关键信息,它的数据结构如下: 15 | 16 | ```go 17 | type Block struct { 18 | Timestamp int64 19 | Data []byte 20 | PrevBlockHash []byte 21 | Hash []byte 22 | } 23 | ``` 24 | 25 | 字段 | 解释 26 | :----: | :---- 27 | `Timestamp` | 当前时间戳,也就是区块创建的时间 28 | `PrevBlockHash` | 前一个块的哈希,即父哈希 29 | `Hash` | 当前块的哈希 30 | `Data` | 区块存储的实际有效信息,也就是交易 31 | 32 | 我们这里的 `Timestamp`,`PrevBlockHash`, `Hash`,在比特币技术规范中属于区块头(block header),区块头是一个单独的数据结构。 33 | 完整的 [比特币的区块头(block header)结构](https://en.bitcoin.it/wiki/Block_hashing_algorithm) 如下: 34 | 35 | Field | Purpose | Updated when... | Size (Bytes) 36 | :---- | :---- | :---- | :---- 37 | Version | Block version number | You upgrade the software and it specifies a new version | 4 38 | hashPrevBlock | 256-bit hash of the previous block header | A new block comes in | 32 39 | hashMerkleRoot | 256-bit hash based on all of the transactions in the block | A transaction is accepted | 32 40 | Time | Current timestamp as seconds since 1970-01-01T00:00 UTC | Every few seconds | 4 41 | Bits | Current target in compact format | The difficulty is adjusted | 4 42 | Nonce | 32-bit number (starts at 0) | A hash is tried (increments) | 4 43 | 44 | 下面是比特币的 golang 实现 btcd 的 [BlockHeader](https://github.com/btcsuite/btcd/blob/01f26a142be8a55b06db04da906163cd9c31be2b/wire/blockheader.go#L20-L41) 定义: 45 | 46 | ```go 47 | // BlockHeader defines information about a block and is used in the bitcoin 48 | // block (MsgBlock) and headers (MsgHeaders) messages. 49 | type BlockHeader struct { 50 | // Version of the block. This is not the same as the protocol version. 51 | Version int32 52 | 53 | // Hash of the previous block in the block chain. 54 | PrevBlock chainhash.Hash 55 | 56 | // Merkle tree reference to hash of all transactions for the block. 57 | MerkleRoot chainhash.Hash 58 | 59 | // Time the block was created. This is, unfortunately, encoded as a 60 | // uint32 on the wire and therefore is limited to 2106. 61 | Timestamp time.Time 62 | 63 | // Difficulty target for the block. 64 | Bits uint32 65 | 66 | // Nonce used to generate the block. 67 | Nonce uint32 68 | } 69 | ``` 70 | 71 | 而我们的 `Data`, 在比特币中对应的是交易,是另一个单独的数据结构。为了简便起见,目前将这两个数据结构放在了一起。在真正的比特币中,[区块](https://en.bitcoin.it/wiki/Block#Block_structure) 的数据结构如下: 72 | 73 | Field | Description | Size 74 | :---- | :---- | :---- 75 | Magic no | value always 0xD9B4BEF9 | 4 bytes 76 | Blocksize | number of bytes following up to end of block | 4 bytes 77 | Blockheader | consists of 6 items | 80 bytes 78 | Transaction counter | positive integer VI = VarInt | 1 - 9 bytes 79 | transactions | the (non empty) list of transactions | -many transactions 80 | 81 | 在我们的简化版区块中,还有一个 `Hash` 字段,那么,要如何计算哈希呢?哈希计算,是区块链一个非常重要的部分。正是由于它,才保证了区块链的安全。计算一个哈希,是在计算上非常困难的一个操作。即使在高速电脑上,也要耗费很多时间 (这就是为什么人们会购买 GPU,FPGA,ASIC 来挖比特币) 。这是一个架构上有意为之的设计,它故意使得加入新的区块十分困难,继而保证区块一旦被加入以后,就很难再进行修改。在接下来的内容中,我们将会讨论和实现这个机制。 82 | 83 | 目前,我们仅取了 `Block` 结构的部分字段(`Timestamp`, `Data` 和 `PrevBlockHash`),并将它们相互拼接起来,然后在拼接后的结果上计算一个 SHA-256,然后就得到了哈希. 84 | 85 | ``` 86 | Hash = SHA256(PrevBlockHash + Timestamp + Data) 87 | ``` 88 | 89 | 在 `SetHash` 方法中完成这些操作: 90 | 91 | ```go 92 | func (b *Block) SetHash() { 93 | timestamp := []byte(strconv.FormatInt(b.Timestamp, 10)) 94 | headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{}) 95 | hash := sha256.Sum256(headers) 96 | 97 | b.Hash = hash[:] 98 | } 99 | ``` 100 | 101 | 接下来,按照 Golang 的惯例,我们会实现一个用于简化创建区块的函数 `NewBlock`: 102 | 103 | ```go 104 | func NewBlock(data string, prevBlockHash []byte) *Block { 105 | block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}} 106 | block.SetHash() 107 | return block 108 | } 109 | ``` 110 | 111 | ## 区块链 112 | 113 | 有了区块,下面让我们来实现区块**链**。本质上,区块链就是一个有着特定结构的数据库,是一个有序,每一个块都连接到前一个块的链表。也就是说,区块按照插入的顺序进行存储,每个块都与前一个块相连。这样的结构,能够让我们快速地获取链上的最新块,并且高效地通过哈希来检索一个块。 114 | 115 | 在 Golang 中,可以通过一个 array 和 map 来实现这个结构:array 存储有序的哈希(Golang 中 array 是有序的),map 存储 **hash -> block** 对(Golang 中, map 是无序的)。 但是在基本的原型阶段,我们只用到了 array,因为现在还不需要通过哈希来获取块。 116 | 117 | ```go 118 | type Blockchain struct { 119 | blocks []*Block 120 | } 121 | ``` 122 | 123 | 这就是我们的第一个区块链!是不是出乎意料地简单? 就是一个 `Block` 数组。 124 | 125 | 现在,让我们能够给它添加一个区块: 126 | 127 | ```go 128 | func (bc *Blockchain) AddBlock(data string) { 129 | prevBlock := bc.blocks[len(bc.blocks)-1] 130 | newBlock := NewBlock(data, prevBlock.Hash) 131 | bc.blocks = append(bc.blocks, newBlock) 132 | } 133 | ``` 134 | 135 | 结束!不过,就这样就完成了吗? 136 | 137 | 为了加入一个新的块,我们必须要有一个已有的块,但是,初始状态下,我们的链是空的,一个块都没有!所以,在任何一个区块链中,都必须至少有一个块。这个块,也就是链中的第一个块,通常叫做创世块(**genesis block**). 让我们实现一个方法来创建创世块: 138 | 139 | ```go 140 | func NewGenesisBlock() *Block { 141 | return NewBlock("Genesis Block", []byte{}) 142 | } 143 | ``` 144 | 145 | 现在,我们可以实现一个函数来创建有创世块的区块链: 146 | 147 | ```go 148 | func NewBlockchain() *Blockchain { 149 | return &Blockchain{[]*Block{NewGenesisBlock()}} 150 | } 151 | ``` 152 | 153 | 检查一个我们的区块链是否如期工作: 154 | 155 | ```go 156 | func main() { 157 | bc := NewBlockchain() 158 | 159 | bc.AddBlock("Send 1 BTC to Ivan") 160 | bc.AddBlock("Send 2 more BTC to Ivan") 161 | 162 | for _, block := range bc.blocks { 163 | fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) 164 | fmt.Printf("Data: %s\n", block.Data) 165 | fmt.Printf("Hash: %x\n", block.Hash) 166 | fmt.Println() 167 | } 168 | } 169 | ``` 170 | 171 | 输出: 172 | 173 | ```bash 174 | Prev. hash: 175 | Data: Genesis Block 176 | Hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168 177 | 178 | Prev. hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168 179 | Data: Send 1 BTC to Ivan 180 | Hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1 181 | 182 | Prev. hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1 183 | Data: Send 2 more BTC to Ivan 184 | Hash: 561237522bb7fcfbccbc6fe0e98bbbde7427ffe01c6fb223f7562288ca2295d1 185 | ``` 186 | 187 | ## 总结 188 | 189 | 我们创建了一个非常简单的区块链原型:它仅仅是一个数组构成的一系列区块,每个块都与前一个块相关联。真实的区块链要比这复杂得多。在我们的区块链中,加入新的块非常简单,也很快,但是在真实的区块链中,加入新的块需要很多工作:你必须要经过十分繁重的计算(这个机制叫做工作量证明),来获得添加一个新块的权力。并且,区块链是一个分布式数据库,并且没有单一决策者。因此,要加入一个新块,必须要被网络的其他参与者确认和同意(这个机制叫做共识(consensus))。还有一点,我们的区块链还没有任何的交易! 190 | 191 | 进入 src 目录查看代码,执行 `make` 即可运行: 192 | 193 | ```bash 194 | $ cd src 195 | $ make 196 | ==> Go build 197 | ==> Running 198 | Prev. hash: 199 | Data: Genesis Block 200 | Hash: 4693b71eee96760de4b0f051083376dcbed2f0711a44294ee5fd42fbeacc9579 201 | 202 | Prev. hash: 4693b71eee96760de4b0f051083376dcbed2f0711a44294ee5fd42fbeacc9579 203 | Data: Send 1 BTC to Ivan 204 | Hash: 839380a2d0af1dc4686f16ade5423fecdc5f287db9322d9e18adcb4071e7c8ff 205 | 206 | Prev. hash: 839380a2d0af1dc4686f16ade5423fecdc5f287db9322d9e18adcb4071e7c8ff 207 | Data: Send 2 more BTC to Ivan 208 | Hash: b38052a029bd2b1b9d4bb478af45b4c88605e99bc64e49031ba06d21ad4b0b38 209 | ``` 210 | 211 | 参考: 212 | 213 | [1] [Block hashing algorithm](https://en.bitcoin.it/wiki/Block_hashing_algorithm) 214 | 215 | [2] [Building Blockchain in Go. Part 1: Basic Prototype](https://jeiwan.cc/posts/building-blockchain-in-go-part-1/) 216 | 217 | --- 218 | 219 | bitcoin wiki 的[区块](https://en.bitcoin.it/wiki/Block)结构: 220 | 221 | Field | Description | Size 222 | :----: | :----: | :----: 223 | Magic no | value always 0xD9B4BEF9 | 4 bytes 224 | Blocksize | number of bytes following up to end of block | 4 bytes 225 | Blockheader | consists of 6 items | 80 bytes 226 | Transaction | counter positive integer VI = VarInt | 1 - 9 bytes 227 | transactions | the (non empty) list of transactions | -many transactions 228 | 229 | ---- 230 | 231 | - 上一节: [区块](basic-prototype.md) 232 | - 下一节: [工作量证明](../part-2/proof-of-work.md) 233 | -------------------------------------------------------------------------------- /content/part-1/src/Makefile: -------------------------------------------------------------------------------- 1 | BINARY := blockchain 2 | 3 | all: build run 4 | 5 | build: 6 | @echo "==> Go build" 7 | @go build -o $(BINARY) 8 | 9 | run: 10 | @echo "==> Running" 11 | @./$(BINARY) 12 | 13 | .PHONY: build run 14 | -------------------------------------------------------------------------------- /content/part-1/src/block.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | // Block 由区块头和交易两部分构成 11 | // Timestamp, PrevBlockHash, Hash 属于区块头(block header) 12 | // Timestamp : 当前时间戳,也就是区块创建的时间 13 | // PrevBlockHash : 前一个块的哈希 14 | // Hash : 当前块的哈希 15 | // Data : 区块实际存储的信息,比特币中也就是交易 16 | type Block struct { 17 | Timestamp int64 18 | PrevBlockHash []byte 19 | Hash []byte 20 | Data []byte 21 | } 22 | 23 | // NewBlock 用于生成新块,参数需要 Data 与 PrevBlockHash 24 | // 当前块的哈希会基于 Data 和 PrevBlockHash 计算得到 25 | func NewBlock(data string, prevBlockHash []byte) *Block { 26 | block := &Block{ 27 | Timestamp: time.Now().Unix(), 28 | PrevBlockHash: prevBlockHash, 29 | Hash: []byte{}, 30 | Data: []byte(data)} 31 | 32 | block.SetHash() 33 | 34 | return block 35 | } 36 | 37 | // SetHash 设置当前块哈希 38 | // Hash = sha256(PrevBlockHash + Data + Timestamp) 39 | func (b *Block) SetHash() { 40 | timestamp := []byte(strconv.FormatInt(b.Timestamp, 10)) 41 | headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{}) 42 | hash := sha256.Sum256(headers) 43 | 44 | b.Hash = hash[:] 45 | } 46 | 47 | // NewGenesisBlock 生成创世块 48 | func NewGenesisBlock() *Block { 49 | return NewBlock("Genesis Block", []byte{}) 50 | } 51 | -------------------------------------------------------------------------------- /content/part-1/src/blockchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // BlockChain 是一个 Block 指针数组 4 | type BlockChain struct { 5 | blocks []*Block 6 | } 7 | 8 | // NewBlockChain 创建一个有创世块的链 9 | func NewBlockChain() *BlockChain { 10 | return &BlockChain{[]*Block{NewGenesisBlock()}} 11 | } 12 | 13 | // AddBlock 向链中加入一个新块 14 | // data 在实际中就是交易 15 | func (bc *BlockChain) AddBlock(data string) { 16 | prevBlock := bc.blocks[len(bc.blocks)-1] 17 | newBlock := NewBlock(data, prevBlock.Hash) 18 | bc.blocks = append(bc.blocks, newBlock) 19 | } 20 | -------------------------------------------------------------------------------- /content/part-1/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | bc := NewBlockChain() 9 | 10 | bc.AddBlock("Send 1 BTC to Ivan") 11 | bc.AddBlock("Send 2 more BTC to Ivan") 12 | 13 | for _, block := range bc.blocks { 14 | fmt.Printf("Prev hash: %x\n", block.PrevBlockHash) 15 | fmt.Printf("Data: %s\n", block.Data) 16 | fmt.Printf("Hash: %x\n", block.Hash) 17 | fmt.Println() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /content/part-2/proof-of-work.md: -------------------------------------------------------------------------------- 1 | 工作量证明 2 | ========== 3 | 4 | 在上一节,我们构造了一个非常简单的数据结构 -- 区块,它也是整个区块链数据库的核心。目前所完成的区块链原型,已经可以通过链式关系把区块相互关联起来:每个块都与前一个块相关联。 5 | 6 | 但是,当前实现的区块链有一个巨大的缺陷:向链中加入区块太容易,也太廉价了。而区块链和比特币的其中一个核心就是,要想加入新的区块,必须先完成一些非常困难的工作。在本文,我们将会弥补这个缺陷。 7 | 8 | ## 工作量证明 9 | 10 | 区块链的一个关键点就是,一个人必须经过一系列困难的工作,才能将数据放入到区块链中。正是由于这种困难的工作,才保证了区块链的安全和一致。此外,完成这个工作的人,也会获得相应奖励(这也就是通过挖矿获得币)。 11 | 12 | 这个机制与生活现象非常类似:一个人必须通过努力工作,才能够获得回报或者奖励,用以支撑他们的生活。在区块链中,是通过网络中的参与者(矿工)不断的工作来支撑起了整个网络。矿工不断地向区块链中加入新块,然后获得相应的奖励。在这种机制的作用下,新生成的区块能够被安全地加入到区块链中,它维护了整个区块链数据库的稳定性。值得注意的是,完成了这个工作的人必须要证明这一点,即他必须要证明他的确完成了这些工作。 13 | 14 | 整个 “努力工作并进行证明” 的机制,就叫做工作量证明(proof-of-work)。要想完成工作非常地不容易,因为这需要大量的计算能力:即便是高性能计算机,也无法在短时间内快速完成。另外,这个工作的困难度会随着时间不断增长,以保持每 10 分钟出 1 个新块的速度。**在比特币中,这个工作就是找到一个块的哈希**,同时这个哈希满足了一些必要条件。这个哈希,也就充当了证明的角色。因此,寻求证明(寻找有效哈希),就是矿工实际要做的事情。 15 | 16 | ## 哈希计算 17 | 18 | 在本节,我们会讨论哈希计算。如果你已经熟悉了这个概念,可以直接跳过。 19 | 20 | 获得指定数据的一个哈希值的过程,就叫做哈希计算。一个哈希,就是对所计算数据的一个唯一表示。对于一个哈希函数,输入任意大小的数据,它会输出一个固定大小的哈希值。下面是哈希的几个关键特性: 21 | 22 | 1. 无法从一个哈希值恢复原始数据。也就是说,哈希并不是加密。 23 | 2. 对于特定的数据,只能有一个哈希,并且这个哈希是唯一的。 24 | 3. 即使是仅仅改变输入数据中的一个字节,也会导致输出一个完全不同的哈希。 25 | 26 | ![hashing](../images/127313-e9b0730b1798704d.png) 27 | 28 | 哈希函数被广泛用于检测数据的一致性。软件提供者常常在除了提供软件包以外,还会发布校验和。当下载完一个文件以后,你可以用哈希函数对下载好的文件计算一个哈希,并与作者提供的哈希进行比较,以此来保证文件下载的完整性。 29 | 30 | 在区块链中,哈希被用于保证一个块的一致性。哈希算法的输入数据包含了前一个块的哈希,因此使得不太可能(或者,至少很困难)去修改链中的一个块:因为如果一个人想要修改前面一个块的哈希,那么他必须要重新计算这个块以及后面所有块的哈希。 31 | 32 | ## Hashcash 33 | 34 | 比特币使用 [Hashcash](https://en.wikipedia.org/wiki/Hashcash) ,一个最初用来防止垃圾邮件的工作量证明算法。它可以被分解为以下步骤: 35 | 36 | 1. 取一些公开的数据(比如,如果是 email 的话,它可以是接收者的邮件地址;在比特币中,它是区块头) 37 | 2. 给这个公开数据添加一个计数器。计数器默认从 0 开始 38 | 3. 将 **data(数据)** 和 **counter(计数器)** 组合到一起,获得一个哈希 39 | 4. 检查哈希是否符合一定的条件: 40 | 1. 如果符合条件,结束 41 | 2. 如果不符合,增加计数器,重复步骤 3-4 42 | 43 | 因此,这是一个暴力算法:改变计数器,计算新的哈希,检查,增加计数器,计算哈希,检查,如此往复。这也是为什么说它的计算成本很高,因为这一步需要如此反复不断地计算和检查。 44 | 45 | 现在,让我们来仔细看一下一个哈希要满足的必要条件。在原始的 Hashcash 实现中,它的要求是 “一个哈希的前 20 位必须是 0”。在比特币中,这个要求会随着时间而不断变化。因为按照设计,必须保证每 10 分钟生成一个块,而不论计算能力会随着时间增长,或者是会有越来越多的矿工进入网络,所以需要动态调整这个必要条件。 46 | 47 | 为了阐释这一算法,我从前一个例子(“I like donuts”)中取得数据,并且找到了一个前 3 个字节是全是 0 的哈希。 48 | 49 | ![a hash that starts with 3 zero-bytes](../images/127313-98fa6d44aad3701f.png) 50 | 51 | **ca07ca** 是计数器的 16 进制值,十进制的话是 13240266. 52 | 53 | ## 实现 54 | 55 | 好了,完成了理论层面,来动手写代码吧!首先,定义挖矿的难度值: 56 | 57 | ```go 58 | const targetBits = 24 59 | ``` 60 | 61 | 在比特币中,当一个块被挖出来以后,“target bits” 代表了区块头里存储的难度,也就是开头有多少个 0。这里的 24 指的是算出来的哈希前 24 位必须是 0,如果用 16 进制表示,就是前 6 位必须是 0,这一点从最后的输出可以看出来。目前我们并不会实现一个动态调整目标的算法,所以将难度定义为一个全局的常量即可。 62 | 63 | 24 其实是一个可以任意取的数字,其目的只是为了有一个目标(target)而已,这个目标占据不到 256 位的内存空间。同时,我们想要有足够的差异性,但是又不至于大的过分,因为差异性越大,就越难找到一个合适的哈希。 64 | 65 | ```go 66 | type ProofOfWork struct { 67 | block *Block 68 | target *big.Int 69 | } 70 | 71 | func NewProofOfWork(b *Block) *ProofOfWork { 72 | target := big.NewInt(1) 73 | target.Lsh(target, uint(256-targetBits)) 74 | 75 | pow := &ProofOfWork{b, target} 76 | 77 | return pow 78 | } 79 | ``` 80 | 81 | 这里,我们构造了 **ProofOfWork** 结构,里面存储了指向一个块(`block`)和一个目标(`target`)的指针。这里的 “目标” ,也就是前一节中所描述的必要条件。这里使用了一个 [大整数](https://golang.org/pkg/math/big/) ,我们会将哈希与目标进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标。 82 | 83 | 在 **NewProofOfWork** 函数中,我们将 **big.Int** 初始化为 1,然后左移 `256 - targetBits` 位。**256** 是一个 SHA-256 哈希的位数,我们将要使用的是 SHA-256 哈希算法。**target(目标)** 的 16 进制形式为: 84 | 85 | ``` 86 | 0x10000000000000000000000000000000000000000000000000000000000 87 | ``` 88 | 89 | 它在内存上占据了 29 个字节。下面是与前面例子哈希的形式化比较: 90 | 91 | ``` 92 | 0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 93 | 0000010000000000000000000000000000000000000000000000000000000000 94 | 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca 95 | ``` 96 | 97 | 第一个哈希(基于 “I like donuts” 计算)比目标要大,因此它并不是一个有效的工作量证明。第二个哈希(基于 “I like donutsca07ca” 计算)比目标要小,所以是一个有效的证明。 98 | 99 | 译者注:上面的形式化比较有些“言不符实”,其实它应该并非由 “I like donuts” 而来,但是原文表达的意思是没问题的,可能是疏忽而已。下面是我做的一个小实验: 100 | 101 | ```go 102 | package main 103 | 104 | import ( 105 | "crypto/sha256" 106 | "fmt" 107 | "math/big" 108 | ) 109 | 110 | func main() { 111 | 112 | data1 := []byte("I like donuts") 113 | data2 := []byte("I like donutsca07ca") 114 | targetBits := 24 115 | target := big.NewInt(1) 116 | target.Lsh(target, uint(256-targetBits)) 117 | fmt.Printf("%x\n", sha256.Sum256(data1)) 118 | fmt.Printf("%64x\n", target) 119 | fmt.Printf("%x\n", sha256.Sum256(data2)) 120 | 121 | } 122 | ``` 123 | 124 | 输出: 125 | 126 | ![experiment](../images/127313-500001f22e8ed4a6.png) 127 | 128 | 你可以把目标想象为一个范围的上界:如果一个数(由哈希转换而来)比上界要小,那么是有效的,反之无效。因为要求比上界要小,所以会导致有效数字并不会很多。因此,也就需要通过一些困难的工作(一系列反复地计算),才能找到一个有效的数字。 129 | 130 | 现在,我们需要有数据来进行哈希,准备数据: 131 | 132 | ```go 133 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 134 | data := bytes.Join( 135 | [][]byte{ 136 | pow.block.PrevBlockHash, 137 | pow.block.Data, 138 | IntToHex(pow.block.Timestamp), 139 | IntToHex(int64(targetBits)), 140 | IntToHex(int64(nonce)), 141 | }, 142 | []byte{}, 143 | ) 144 | 145 | return data 146 | } 147 | ``` 148 | 149 | 这个部分比较直观:只需要将 target ,nonce 与 Block 进行合并。这里的 `nonce`,就是上面 Hashcash 所提到的计数器,它是一个密码学术语。 150 | 151 | 很好,到这里,所有的准备工作就完成了,下面来实现 PoW 算法的核心: 152 | 153 | ```go 154 | func (pow *ProofOfWork) Run() (int, []byte) { 155 | var hashInt big.Int 156 | var hash [32]byte 157 | nonce := 0 158 | 159 | fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) 160 | for nonce < maxNonce { 161 | data := pow.prepareData(nonce) 162 | hash = sha256.Sum256(data) 163 | hashInt.SetBytes(hash[:]) 164 | 165 | if hashInt.Cmp(pow.target) == -1 { 166 | fmt.Printf("\r%x", hash) 167 | break 168 | } else { 169 | nonce++ 170 | } 171 | } 172 | fmt.Print("\n\n") 173 | 174 | return nonce, hash[:] 175 | } 176 | ``` 177 | 178 | 首先我们对变量进行初始化: 179 | 180 | - `HashInt` 是 `hash` 的整形表示; 181 | - `nonce` 是计数器。 182 | 183 | 然后开始一个 “无限” 循环:`maxNonce` 对这个循环进行了限制, 它等于 `math.MaxInt64`,这是为了避免 `nonce` 可能出现的溢出。尽管我们 PoW 的难度很小,以至于计数器其实不太可能会溢出,但最好还是以防万一检查一下。 184 | 185 | 在这个循环中,我们做的事情有: 186 | 187 | 1. 准备数据 188 | 2. 用 SHA-256 对数据进行哈希 189 | 3. 将哈希转换成一个大整数 190 | 4. 将这个大整数与目标进行比较 191 | 192 | 跟之前所讲的一样简单。现在我们可以移除 `Block` 的 `SetHash` 方法,然后修改 `NewBlock` 函数: 193 | 194 | ```go 195 | func NewBlock(data string, prevBlockHash []byte) *Block { 196 | block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} 197 | pow := NewProofOfWork(block) 198 | nonce, hash := pow.Run() 199 | 200 | block.Hash = hash[:] 201 | block.Nonce = nonce 202 | 203 | return block 204 | } 205 | ``` 206 | 207 | 在这里,你可以看到 `nonce` 被保存为 `Block` 的一个属性。这是十分有必要的,因为待会儿我们对这个工作量进行验证时会用到 `nonce` 。`Block` 结构现在看起来像是这样: 208 | 209 | ```go 210 | type Block struct { 211 | Timestamp int64 212 | Data []byte 213 | PrevBlockHash []byte 214 | Hash []byte 215 | Nonce int 216 | } 217 | ``` 218 | 219 | 好了!现在让我们来运行一下是否正常工作: 220 | 221 | ```go 222 | Mining the block containing "Genesis Block" 223 | 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 224 | 225 | Mining the block containing "Send 1 BTC to Ivan" 226 | 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 227 | 228 | Mining the block containing "Send 2 more BTC to Ivan" 229 | 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe 230 | 231 | Prev. hash: 232 | Data: Genesis Block 233 | Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 234 | 235 | Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 236 | Data: Send 1 BTC to Ivan 237 | Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 238 | 239 | Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 240 | Data: Send 2 more BTC to Ivan 241 | Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe 242 | ``` 243 | 244 | 成功了!你可以看到每个哈希都是 3 个字节的 0 开始,并且获得这些哈希需要花费一些时间。 245 | 246 | 还剩下一件事情需要做,对工作量证明进行验证: 247 | 248 | ```go 249 | func (pow *ProofOfWork) Validate() bool { 250 | var hashInt big.Int 251 | 252 | data := pow.prepareData(pow.block.Nonce) 253 | hash := sha256.Sum256(data) 254 | hashInt.SetBytes(hash[:]) 255 | 256 | isValid := hashInt.Cmp(pow.target) == -1 257 | 258 | return isValid 259 | } 260 | ``` 261 | 262 | 这里,就是我们就用到了上面保存的 `nonce`。 263 | 264 | 再来检测一次是否正常工作: 265 | 266 | ```go 267 | func main() { 268 | ... 269 | 270 | for _, block := range bc.blocks { 271 | ... 272 | pow := NewProofOfWork(block) 273 | fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 274 | fmt.Println() 275 | } 276 | } 277 | ``` 278 | 279 | 输出: 280 | 281 | ```go 282 | ... 283 | 284 | Prev. hash: 285 | Data: Genesis Block 286 | Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 287 | PoW: true 288 | 289 | Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 290 | Data: Send 1 BTC to Ivan 291 | Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b 292 | PoW: true 293 | 294 | Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b 295 | Data: Send 2 more BTC to Ivan 296 | Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a 297 | PoW: true 298 | ``` 299 | 300 | 从下图可以看出,这次我们产生三个块花费了一分多钟,比没有工作量证明之前慢了很多(也就是成本高了很多): 301 | 302 | ![output](../images/127313-3761d41e37f5a6de.png) 303 | 304 | ## 总结 305 | 306 | 我们离真正的区块链又进了一步:现在需要经过一些困难的工作才能加入新的块,因此挖矿就有可能了。但是,它仍然缺少一些至关重要的特性:区块链数据库并不是持久化的,没有钱包,地址,交易,也没有共识机制。不过,所有的这些,我们都会在接下来的文章中实现,现在,愉快地挖矿吧! 307 | 308 | 参考: 309 | 310 | - [Full source codes](https://github.com/Jeiwan/blockchain_go/tree/part_2) 311 | 312 | - [Blockchain hashing algorithm](https://en.bitcoin.it/wiki/Block_hashing_algorithm) 313 | 314 | - [Proof of work](https://en.bitcoin.it/wiki/Proof_of_work) 315 | 316 | - [Hashcash](https://en.bitcoin.it/wiki/Hashcash) 317 | 318 | - [Building Blockchain in Go. Part 2: Proof-of-Work](https://jeiwan.cc/posts/building-blockchain-in-go-part-2/) 319 | 320 | - [part_2](https://github.com/Jeiwan/blockchain_go/tree/part_2) 321 | 322 | ---- 323 | 324 | - 上一节: [基本原型](../part-1/basic-prototype.md) 325 | - 下一节: [持久化](../part-3/persistence-and-cli.md) 326 | -------------------------------------------------------------------------------- /content/part-2/src/Makefile: -------------------------------------------------------------------------------- 1 | BINARY := blockchain 2 | 3 | all: build run 4 | 5 | build: 6 | @echo "==> Go build" 7 | @go build -o $(BINARY) 8 | 9 | run: 10 | @echo "==> Run" 11 | @./$(BINARY) 12 | 13 | .PHONY: build run 14 | -------------------------------------------------------------------------------- /content/part-2/src/block.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Nonce 在对工作量证明进行验证时用到 8 | type Block struct { 9 | Timestamp int64 10 | PrevBlockHash []byte 11 | Hash []byte 12 | Data []byte 13 | Nonce int 14 | } 15 | 16 | // 创建新块时需要运行工作量证明找到有效哈希 17 | func NewBlock(data string, prevBlockHash []byte) *Block { 18 | block := &Block{ 19 | Timestamp: time.Now().Unix(), 20 | PrevBlockHash: prevBlockHash, 21 | Hash: []byte{}, 22 | Data: []byte(data), 23 | Nonce: 0} 24 | pow := NewProofOfWork(block) 25 | nonce, hash := pow.Run() 26 | 27 | block.Hash = hash[:] 28 | block.Nonce = nonce 29 | 30 | return block 31 | } 32 | 33 | func NewGenesisBlock() *Block { 34 | return NewBlock("Genesis Block", []byte{}) 35 | } 36 | -------------------------------------------------------------------------------- /content/part-2/src/blockchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type BlockChain struct { 4 | blocks []*Block 5 | } 6 | 7 | func NewBlockChain() *BlockChain { 8 | return &BlockChain{[]*Block{NewGenesisBlock()}} 9 | } 10 | 11 | func (bc *BlockChain) AddBlock(data string) { 12 | prevBlock := bc.blocks[len(bc.blocks)-1] 13 | newBlock := NewBlock(data, prevBlock.Hash) 14 | bc.blocks = append(bc.blocks, newBlock) 15 | } 16 | -------------------------------------------------------------------------------- /content/part-2/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | func main() { 9 | bc := NewBlockChain() 10 | 11 | bc.AddBlock("Send 1 BTC to Ivan") 12 | bc.AddBlock("Send 2 more BTC to Ivan") 13 | 14 | for _, block := range bc.blocks { 15 | fmt.Printf("Prev hash: %x\n", block.PrevBlockHash) 16 | fmt.Printf("Data: %s\n", block.Data) 17 | fmt.Printf("Hash: %x\n", block.Hash) 18 | pow := NewProofOfWork(block) 19 | fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 20 | fmt.Println() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /content/part-2/src/proofofwork.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | ) 10 | 11 | // 难度值,这里表示哈希的前 24 位必须是 0 12 | const targetBits = 24 13 | 14 | const maxNonce = math.MaxInt64 15 | 16 | // 每个块的工作量都必须要证明,所有有个指向 Block 的指针 17 | // target 是目标,我们最终要找的哈希必须要小于目标 18 | type ProofOfWork struct { 19 | block *Block 20 | target *big.Int 21 | } 22 | 23 | // target 等于 1 左移 256 - targetBits 位 24 | func NewProofOfWork(b *Block) *ProofOfWork { 25 | target := big.NewInt(1) 26 | target.Lsh(target, uint(256-targetBits)) 27 | 28 | pow := &ProofOfWork{b, target} 29 | 30 | return pow 31 | } 32 | 33 | // 工作量证明用到的数据有:PrevBlockHash, Data, Timestamp, targetBits, nonce 34 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 35 | data := bytes.Join( 36 | [][]byte{ 37 | pow.block.PrevBlockHash, 38 | pow.block.Data, 39 | IntToHex(pow.block.Timestamp), 40 | IntToHex(int64(targetBits)), 41 | IntToHex(int64(nonce)), 42 | }, 43 | []byte{}, 44 | ) 45 | 46 | return data 47 | } 48 | 49 | // 工作量证明的核心就是寻找有效哈希 50 | func (pow *ProofOfWork) Run() (int, []byte) { 51 | var hashInt big.Int 52 | var hash [32]byte 53 | nonce := 0 54 | 55 | fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) 56 | for nonce < maxNonce { 57 | data := pow.prepareData(nonce) 58 | 59 | hash = sha256.Sum256(data) 60 | hashInt.SetBytes(hash[:]) 61 | 62 | if hashInt.Cmp(pow.target) == -1 { 63 | fmt.Printf("\r%x", hash) 64 | break 65 | } else { 66 | nonce++ 67 | } 68 | } 69 | fmt.Print("\n\n") 70 | 71 | return nonce, hash[:] 72 | } 73 | 74 | // 验证工作量,只要哈希小于目标就是有效工作量 75 | func (pow *ProofOfWork) Validate() bool { 76 | var hashInt big.Int 77 | 78 | data := pow.prepareData(pow.block.Nonce) 79 | hash := sha256.Sum256(data) 80 | hashInt.SetBytes(hash[:]) 81 | 82 | isValid := hashInt.Cmp(pow.target) == -1 83 | 84 | return isValid 85 | } 86 | -------------------------------------------------------------------------------- /content/part-2/src/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "log" 7 | ) 8 | 9 | // 将一个 int64 转化为一个字节数组(byte array) 10 | func IntToHex(num int64) []byte { 11 | buff := new(bytes.Buffer) 12 | err := binary.Write(buff, binary.BigEndian, num) 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | 17 | return buff.Bytes() 18 | } 19 | -------------------------------------------------------------------------------- /content/part-3/persistence-and-cli.md: -------------------------------------------------------------------------------- 1 | 持久化和命令行接口 2 | ================== 3 | 4 | ## 引言 5 | 6 | 到目前为止,我们已经构建了一个有工作量证明机制的区块链。有了工作量证明,挖矿也就有了着落。虽然目前距离一个有着完整功能的区块链越来越近了,但是它仍然缺少了一些重要的特性。在今天的内容中,我们会将区块链持久化到一个数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作。本质上,区块链是一个分布式数据库,不过,我们暂时先忽略 “分布式” 这个部分,仅专注于 “存储” 这一点。 7 | 8 | ## 选择数据库 9 | 10 | 目前,我们的区块链实现里面并没有用到数据库,而是在每次运行程序时,简单地将区块链存储在内存中。那么一旦程序退出,所有的内容就都消失了。我们没有办法再次使用这条链,也没有办法与其他人共享,所以我们需要把它存储到磁盘上。 11 | 12 | 那么,我们要用哪个数据库呢?实际上,任何一个数据库都可以。在 [比特币原始论文](https://bitcoin.org/bitcoin.pdf) 中,并没有提到要使用哪一个具体的数据库,它完全取决于开发者如何选择。 [Bitcoin Core](https://github.com/bitcoin/bitcoin) ,最初由中本聪发布,现在是比特币的一个参考实现,它使用的是  [LevelDB](https://github.com/google/leveldb)。而我们将要使用的是... 13 | 14 | ## BoltDB 15 | 16 | 因为它: 17 | 18 | 1. 非常简洁 19 | 2. 用 Go 实现 20 | 3. 不需要运行一个服务器 21 | 4. 能够允许我们构造想要的数据结构 22 | 23 | BoltDB GitHub 上的 [README](https://github.com/boltdb/bolt) 是这么说的: 24 | 25 | >Bolt 是一个纯键值存储的 Go 数据库,启发自 Howard Chu 的 LMDB. 它旨在为那些无须一个像 Postgres 和 MySQL 这样有着完整数据库服务器的项目,提供一个简单,快速和可靠的数据库。 26 | > 27 | >由于 Bolt 意在用于提供一些底层功能,简洁便成为其关键所在。它的 API 并不多,并且仅关注值的获取和设置。仅此而已。 28 | 29 | 听起来跟我们的需求完美契合!来快速过一下: 30 | 31 | Bolt 使用键值存储,这意味着它没有像 SQL RDBMS (MySQL,PostgreSQL 等等)的表,没有行和列。相反,数据被存储为键值对(key-value pair,就像 Golang 的 map)。键值对被存储在 bucket 中,这是为了将相似的键值对进行分组(类似 RDBMS 中的表格)。因此,为了获取一个值,你需要知道一个 bucket 和一个键(key)。 32 | 33 | 需要注意的一个事情是,Bolt 数据库没有数据类型:键和值都是字节数组(byte array)。鉴于需要在里面存储 Go 的结构(准确来说,也就是存储**Block(块)**),我们需要对它们进行序列化,也就说,实现一个从 Go struct 转换到一个 byte array 的机制,同时还可以从一个 byte array 再转换回 Go struct。虽然我们将会使用  [encoding/gob](https://golang.org/pkg/encoding/gob/)  来完成这一目标,但实际上也可以选择使用 **JSON**, **XML**, **Protocol Buffers** 等等。之所以选择使用 **encoding/gob**, 是因为它很简单,而且是 Go 标准库的一部分。 34 | 35 | 虽然 BoltDB 的作者出于个人原因已经不在对其维护(见[README](https://github.com/boltdb/bolt/commit/fa5367d20c994db73282594be0146ab221657943)), 不过关系不大,它已经足够稳定了,况且也有活跃的 fork:[coreos/bblot](https://github.com/coreos/bbolt)。 36 | 37 | ## 数据库结构 38 | 39 | 在开始实现持久化的逻辑之前,我们首先需要决定到底要如何在数据库中进行存储。为此,我们可以参考 Bitcoin Core 的做法: 40 | 41 | 简单来说,Bitcoin Core 使用两个 “bucket” 来存储数据: 42 | 43 | 1. 其中一个 bucket 是 **blocks**,它存储了描述一条链中所有块的元数据 44 | 2. 另一个 bucket 是 **chainstate**,存储了一条链的状态,也就是当前所有的未花费的交易输出,和一些元数据 45 | 46 | 此外,出于性能的考虑,Bitcoin Core 将每个区块(block)存储为磁盘上的不同文件。如此一来,就不需要仅仅为了读取一个单一的块而将所有(或者部分)的块都加载到内存中。但是,为了简单起见,我们并不会实现这一点。 47 | 48 | 在 **blocks** 中,**key -> value** 为: 49 | 50 | key | value 51 | :----: | :----: 52 | `b` + 32 字节的 block hash | block index record 53 | `f` + 4 字节的 file number | file information record 54 | `l` + 4 字节的 file number | the last block file number used 55 | `R` + 1 字节的 boolean | 是否正在 reindex 56 | `F` + 1 字节的 flag name length + flag name string | 1 byte boolean: various flags that can be on or off 57 | `t` + 32 字节的 transaction hash | transaction index record 58 | 59 | 在 **chainstate**,**key -> value** 为: 60 | 61 | key | value 62 | :----: | :----: 63 | `c` + 32 字节的 transaction hash | unspent transaction output record for that transaction 64 | `B` | 32 字节的 block hash: the block hash up to which the database represents the unspent transaction outputs 65 | 66 | 详情可见 **[这里](https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_2):_Data_Storage)**。 67 | 68 | 因为目前还没有交易,所以我们只需要 **blocks** bucket。另外,正如上面提到的,我们会将整个数据库存储为单个文件,而不是将区块存储在不同的文件中。所以,我们也不会需要文件编号(file number)相关的东西。最终,我们会用到的键值对有: 69 | 70 | 1. 32 字节的 block-hash -> block 结构 71 | 2. `l` -> 链中最后一个块的 hash 72 | 73 | 这就是实现持久化机制所有需要了解的内容了。 74 | 75 | ## 序列化 76 | 77 | 上面提到,在 BoltDB 中,值只能是 `[]byte` 类型,但是我们想要存储 `Block` 结构。所以,我们需要使用 [encoding/gob](https://golang.org/pkg/encoding/gob/) 来对这些结构进行序列化。 78 | 79 | 让我们来实现 `Block` 的 `Serialize` 方法(为了简洁起见,此处略去了错误处理): 80 | 81 | ```go 82 | func (b *Block) Serialize() []byte { 83 | var result bytes.Buffer 84 | encoder := gob.NewEncoder(&result) 85 | 86 | err := encoder.Encode(b) 87 | 88 | return result.Bytes() 89 | } 90 | ``` 91 | 92 | 这个部分比较直观:首先,我们定义一个 buffer 存储序列化之后的数据。然后,我们初始化一个 gob `encoder` 并对 block 进行编码,结果作为一个字节数组返回。 93 | 94 | 接下来,我们需要一个解序列化的函数,它会接受一个字节数组作为输入,并返回一个 `Block`. 它不是一个方法(method),而是一个单独的函数(function): 95 | 96 | ```go 97 | func DeserializeBlock(d []byte) *Block { 98 | var block Block 99 | 100 | decoder := gob.NewDecoder(bytes.NewReader(d)) 101 | err := decoder.Decode(&block) 102 | 103 | return &block 104 | } 105 | ``` 106 | 107 | 这就是序列化部分的内容了。 108 | 109 | ## 持久化 110 | 111 | 让我们从 `NewBlockchain` 函数开始。在之前的实现中,`NewBlockchain` 会创建一个新的 `Blockchain` 实例,并向其中加入创世块。而现在,我们希望它做的事情有: 112 | 113 | 1. 打开一个数据库文件 114 | 2. 检查文件里面是否已经存储了一个区块链 115 | 3. 如果已经存储了一个区块链: 116 | 1. 创建一个新的 `Blockchain` 实例 117 | 2. 设置 `Blockchain` 实例的 tip 为数据库中存储的最后一个块的哈希 118 | 4. 如果没有区块链: 119 | 1. 创建创世块 120 | 2. 存储到数据库 121 | 3. 将创世块哈希保存为最后一个块的哈希 122 | 4. 创建一个新的 `Blockchain` 实例,初始时 tip 指向创世块(tip 有尾部,尖端的意思,在这里 tip 存储的是最后一个块的哈希) 123 | 124 | 代码大概是这样: 125 | 126 | ```go 127 | func NewBlockchain() *Blockchain { 128 | var tip []byte 129 | db, err := bolt.Open(dbFile, 0600, nil) 130 | 131 | err = db.Update(func(tx *bolt.Tx) error { 132 | b := tx.Bucket([]byte(blocksBucket)) 133 | 134 | if b == nil { 135 | genesis := NewGenesisBlock() 136 | b, err := tx.CreateBucket([]byte(blocksBucket)) 137 | err = b.Put(genesis.Hash, genesis.Serialize()) 138 | err = b.Put([]byte("l"), genesis.Hash) 139 | tip = genesis.Hash 140 | } else { 141 | tip = b.Get([]byte("l")) 142 | } 143 | 144 | return nil 145 | }) 146 | 147 | bc := Blockchain{tip, db} 148 | 149 | return &bc 150 | } 151 | ``` 152 | 153 | 来一段一段地看下代码: 154 | 155 | ```go 156 | db, err := bolt.Open(dbFile, 0600, nil) 157 | ``` 158 | 159 | 这是打开一个 BoltDB 文件的标准做法。注意,即使不存在这样的文件,它也不会返回错误。 160 | 161 | ```go 162 | err = db.Update(func(tx *bolt.Tx) error { 163 | ... 164 | }) 165 | ``` 166 | 167 | 在 BoltDB 中,数据库操作通过一个事务(transaction)进行操作。有两种类型的事务:只读(read-only)和读写(read-write)。这里,打开的是一个读写事务(`db.Update(...)`),因为我们可能会向数据库中添加创世块。 168 | 169 | ```go 170 | b := tx.Bucket([]byte(blocksBucket)) 171 | 172 | if b == nil { 173 | genesis := NewGenesisBlock() 174 | b, err := tx.CreateBucket([]byte(blocksBucket)) 175 | err = b.Put(genesis.Hash, genesis.Serialize()) 176 | err = b.Put([]byte("l"), genesis.Hash) 177 | tip = genesis.Hash 178 | } else { 179 | tip = b.Get([]byte("l")) 180 | } 181 | ``` 182 | 183 | 这里是函数的核心。在这里,我们先获取了存储区块的 bucket:如果存在,就从中读取 `l` 键;如果不存在,就生成创世块,创建 bucket,并将区块保存到里面,然后更新 `l` 键以存储链中最后一个块的哈希。 184 | 185 | 另外,注意创建 `Blockchain` 一个新的方式: 186 | 187 | ```go 188 | bc := Blockchain{tip, db} 189 | ``` 190 | 191 | 这次,我们不在里面存储所有的区块了,而是仅存储区块链的 `tip`。另外,我们存储了一个数据库连接。因为我们想要一旦打开它的话,就让它一直运行,直到程序运行结束。因此,`Blockchain` 的结构现在看起来是这样: 192 | 193 | ```go 194 | type Blockchain struct { 195 | tip []byte 196 | db *bolt.DB 197 | } 198 | ``` 199 | 200 | 接下来我们想要更新的是 `AddBlock` 方法:现在向链中加入区块,就不是像之前向一个数组中加入一个元素那么简单了。从现在开始,我们会将区块存储在数据库里面: 201 | 202 | ```go 203 | func (bc *Blockchain) AddBlock(data string) { 204 | var lastHash []byte 205 | 206 | err := bc.db.View(func(tx *bolt.Tx) error { 207 | b := tx.Bucket([]byte(blocksBucket)) 208 | lastHash = b.Get([]byte("l")) 209 | 210 | return nil 211 | }) 212 | 213 | newBlock := NewBlock(data, lastHash) 214 | 215 | err = bc.db.Update(func(tx *bolt.Tx) error { 216 | b := tx.Bucket([]byte(blocksBucket)) 217 | err := b.Put(newBlock.Hash, newBlock.Serialize()) 218 | err = b.Put([]byte("l"), newBlock.Hash) 219 | bc.tip = newBlock.Hash 220 | 221 | return nil 222 | }) 223 | } 224 | ``` 225 | 226 | 继续来一段一段分解开来: 227 | 228 | ```go 229 | err := bc.db.View(func(tx *bolt.Tx) error { 230 | b := tx.Bucket([]byte(blocksBucket)) 231 | lastHash = b.Get([]byte("l")) 232 | 233 | return nil 234 | }) 235 | ``` 236 | 237 | 这是 BoltDB 事务的另一个类型(只读)。在这里,我们会从数据库中获取最后一个块的哈希,然后用它来挖出一个新的块的哈希: 238 | 239 | ```go 240 | newBlock := NewBlock(data, lastHash) 241 | b := tx.Bucket([]byte(blocksBucket)) 242 | err := b.Put(newBlock.Hash, newBlock.Serialize()) 243 | err = b.Put([]byte("l"), newBlock.Hash) 244 | bc.tip = newBlock.Hash 245 | ``` 246 | 247 | ## 检查区块链 248 | 249 | 现在,产生的所有块都会被保存到一个数据库里面,所以我们可以重新打开一个链,然后向里面加入新块。但是在实现这一点后,我们失去了之前一个非常好的特性:再也无法打印区块链的区块了,因为现在不是将区块存储在一个数组,而是放到了数据库里面。让我们来解决这个问题! 250 | 251 | BoltDB 允许对一个 bucket 里面的所有 key 进行迭代,但是所有的 key 都以字节序进行存储,而且我们想要以区块能够进入区块链中的顺序进行打印。此外,因为我们不想将所有的块都加载到内存中(因为我们的区块链数据库可能很大!或者现在可以假装它可能很大),我们将会一个一个地读取它们。故而,我们需要一个区块链迭代器(`BlockchainIterator`): 252 | 253 | ```go 254 | type BlockchainIterator struct { 255 | currentHash []byte 256 | db *bolt.DB 257 | } 258 | ``` 259 | 260 | 每当要对链中的块进行迭代时,我们就会创建一个迭代器,里面存储了当前迭代的块哈希(`currentHash`)和数据库的连接(`db`)。通过 `db`,迭代器逻辑上被附属到一个区块链上(这里的区块链指的是存储了一个数据库连接的 `Blockchain` 实例),并且通过 `Blockchain` 方法进行创建: 261 | 262 | ```go 263 | func (bc *Blockchain) Iterator() *BlockchainIterator { 264 | bci := &BlockchainIterator{bc.tip, bc.db} 265 | 266 | return bci 267 | } 268 | ``` 269 | 270 | 注意,迭代器的初始状态为链中的 tip,因此区块将从尾到头(创世块为头),也就是从最新的到最旧的进行获取。实际上,**选择一个 tip 就是意味着给一条链“投票”**。一条链可能有多个分支,最长的那条链会被认为是主分支。在获得一个 tip (可以是链中的任意一个块)之后,我们就可以重新构造整条链,找到它的长度和需要构建它的工作。这同样也意味着,一个 tip 也就是区块链的一种标识符。 271 | 272 | `BlockchainIterator` 只会做一件事情:返回链中的下一个块。 273 | 274 | ```go 275 | func (i *BlockchainIterator) Next() *Block { 276 | var block *Block 277 | 278 | err := i.db.View(func(tx *bolt.Tx) error { 279 | b := tx.Bucket([]byte(blocksBucket)) 280 | encodedBlock := b.Get(i.currentHash) 281 | block = DeserializeBlock(encodedBlock) 282 | 283 | return nil 284 | }) 285 | 286 | i.currentHash = block.PrevBlockHash 287 | 288 | return block 289 | } 290 | ``` 291 | 292 | 这就是数据库部分的内容了! 293 | 294 | ## CLI 295 | 296 | 到目前为止,我们的实现还没有提供一个与程序交互的接口:目前只是在 `main` 函数中简单执行了 `NewBlockchain` 和 `bc.AddBlock` 。是时候改变了!现在我们想要拥有这些命令: 297 | 298 | ```go 299 | blockchain_go addblock "Pay 0.031337 for a coffee" 300 | blockchain_go printchain 301 | ``` 302 | 303 | 所有命令行相关的操作都会通过 `CLI` 结构进行处理: 304 | 305 | ```go 306 | type CLI struct { 307 | bc *Blockchain 308 | } 309 | ``` 310 | 311 | 它的 “入口” 是 `Run` 函数: 312 | 313 | ```go 314 | func (cli *CLI) Run() { 315 | cli.validateArgs() 316 | 317 | addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) 318 | printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 319 | 320 | addBlockData := addBlockCmd.String("data", "", "Block data") 321 | 322 | switch os.Args[1] { 323 | case "addblock": 324 | err := addBlockCmd.Parse(os.Args[2:]) 325 | case "printchain": 326 | err := printChainCmd.Parse(os.Args[2:]) 327 | default: 328 | cli.printUsage() 329 | os.Exit(1) 330 | } 331 | 332 | if addBlockCmd.Parsed() { 333 | if *addBlockData == "" { 334 | addBlockCmd.Usage() 335 | os.Exit(1) 336 | } 337 | cli.addBlock(*addBlockData) 338 | } 339 | 340 | if printChainCmd.Parsed() { 341 | cli.printChain() 342 | } 343 | } 344 | ``` 345 | 346 | 我们会使用标准库里面的 [flag](https://golang.org/pkg/flag/) 包来解析命令行参数: 347 | 348 | ```go 349 | addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) 350 | printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 351 | addBlockData := addBlockCmd.String("data", "", "Block data") 352 | ``` 353 | 354 | 首先,我们创建两个子命令: `addblock` 和 `printchain`, 然后给 `addblock` 添加 `-data` 标志。`printchain` 没有任何标志。 355 | 356 | ```go 357 | switch os.Args[1] { 358 | case "addblock": 359 | err := addBlockCmd.Parse(os.Args[2:]) 360 | case "printchain": 361 | err := printChainCmd.Parse(os.Args[2:]) 362 | default: 363 | cli.printUsage() 364 | os.Exit(1) 365 | } 366 | ``` 367 | 368 | 然后,我们检查用户提供的命令,解析相关的 `flag` 子命令: 369 | 370 | ```go 371 | if addBlockCmd.Parsed() { 372 | if *addBlockData == "" { 373 | addBlockCmd.Usage() 374 | os.Exit(1) 375 | } 376 | cli.addBlock(*addBlockData) 377 | } 378 | 379 | if printChainCmd.Parsed() { 380 | cli.printChain() 381 | } 382 | ``` 383 | 384 | 接着检查解析是哪一个子命令,并调用相关函数: 385 | 386 | ```go 387 | func (cli *CLI) addBlock(data string) { 388 | cli.bc.AddBlock(data) 389 | fmt.Println("Success!") 390 | } 391 | 392 | func (cli *CLI) printChain() { 393 | bci := cli.bc.Iterator() 394 | 395 | for { 396 | block := bci.Next() 397 | 398 | fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) 399 | fmt.Printf("Data: %s\n", block.Data) 400 | fmt.Printf("Hash: %x\n", block.Hash) 401 | pow := NewProofOfWork(block) 402 | fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 403 | fmt.Println() 404 | 405 | if len(block.PrevBlockHash) == 0 { 406 | break 407 | } 408 | } 409 | } 410 | ``` 411 | 412 | 这部分内容跟之前的很像,唯一的区别是我们现在使用的是 `BlockchainIterator` 对区块链中的区块进行迭代: 413 | 414 | 记得不要忘了对 `main` 函数作出相应的修改: 415 | 416 | ```go 417 | func main() { 418 | bc := NewBlockchain() 419 | defer bc.db.Close() 420 | 421 | cli := CLI{bc} 422 | cli.Run() 423 | } 424 | ``` 425 | 426 | 注意,无论提供什么命令行参数,都会创建一个新的链。 427 | 428 | 这就是今天的所有内容了! 来看一下是不是如期工作: 429 | 430 | ```go 431 | $ blockchain_go printchain 432 | No existing blockchain found. Creating a new one... 433 | Mining the block containing "Genesis Block" 434 | 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b 435 | 436 | Prev. hash: 437 | Data: Genesis Block 438 | Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b 439 | PoW: true 440 | 441 | $ blockchain_go addblock -data "Send 1 BTC to Ivan" 442 | Mining the block containing "Send 1 BTC to Ivan" 443 | 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 444 | 445 | Success! 446 | 447 | $ blockchain_go addblock -data "Pay 0.31337 BTC for a coffee" 448 | Mining the block containing "Pay 0.31337 BTC for a coffee" 449 | 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 450 | 451 | Success! 452 | 453 | $ blockchain_go printchain 454 | Prev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 455 | Data: Pay 0.31337 BTC for a coffee 456 | Hash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 457 | PoW: true 458 | 459 | Prev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b 460 | Data: Send 1 BTC to Ivan 461 | Hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 462 | PoW: true 463 | 464 | Prev. hash: 465 | Data: Genesis Block 466 | Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b 467 | PoW: true 468 | ``` 469 | 470 | ![test](../images/127313-996c857601ed80a1.png) 471 | 472 | 473 | 参考: 474 | 475 | - [Full source codes](https://github.com/Jeiwan/blockchain_go/tree/part_3) 476 | 477 | - [Bitcoin Core Data Storage](https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_2):_Data_Storage) 478 | 479 | - [boltdb](https://github.com/boltdb/bolt) 480 | 481 | - [encoding/gob](https://golang.org/pkg/encoding/gob/) 482 | 483 | - [flag](https://golang.org/pkg/flag/) 484 | 485 | - [part_3](https://github.com/Jeiwan/blockchain_go/tree/part_3) 486 | 487 | - [Building Blockchain in Go. Part 3: Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/) 488 | 489 | 490 | -------------------------------------------------------------------------------- /content/part-3/src/Makefile: -------------------------------------------------------------------------------- 1 | BINARY := blockchain 2 | 3 | all: build test 4 | 5 | build: deps 6 | @echo "====> Go build" 7 | @go build -o $(BINARY) 8 | 9 | deps: 10 | @go get -u github.com/boltdb/bolt 11 | 12 | test: 13 | ./$(BINARY) printchain 14 | ./$(BINARY) addblock -data "Send 1 BTC to Ivan" 15 | ./$(BINARY) addblock -data "Pay 0.31337 BTC for a coffee" 16 | ./$(BINARY) printchain 17 | 18 | .PHONY: build deps test 19 | -------------------------------------------------------------------------------- /content/part-3/src/block.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "log" 7 | "time" 8 | ) 9 | 10 | type Block struct { 11 | Timestamp int64 12 | Data []byte 13 | PrevBlockHash []byte 14 | Hash []byte 15 | Nonce int 16 | } 17 | 18 | // 将 Block 序列化为一个字节数组 19 | func (b *Block) Serialize() []byte { 20 | var result bytes.Buffer 21 | encoder := gob.NewEncoder(&result) 22 | 23 | err := encoder.Encode(b) 24 | if err != nil { 25 | log.Panic(err) 26 | } 27 | 28 | return result.Bytes() 29 | } 30 | 31 | // 将字节数组反序列化为一个 Block 32 | func DeserializeBlock(d []byte) *Block { 33 | var block Block 34 | 35 | decoder := gob.NewDecoder(bytes.NewReader(d)) 36 | err := decoder.Decode(&block) 37 | if err != nil { 38 | log.Panic(err) 39 | } 40 | 41 | return &block 42 | } 43 | 44 | func NewBlock(data string, prevBlockHash []byte) *Block { 45 | block := &Block{ 46 | Timestamp: time.Now().Unix(), 47 | Data: []byte(data), 48 | PrevBlockHash: prevBlockHash, 49 | Hash: []byte{}, 50 | Nonce: 0} 51 | pow := NewProofOfWork(block) 52 | nonce, hash := pow.Run() 53 | 54 | block.Hash = hash[:] 55 | block.Nonce = nonce 56 | 57 | return block 58 | } 59 | 60 | func NewGenesisBlock() *Block { 61 | return NewBlock("Genesis Block", []byte{}) 62 | } 63 | -------------------------------------------------------------------------------- /content/part-3/src/blockchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/boltdb/bolt" 8 | ) 9 | 10 | const dbFile = "blockchain.db" 11 | const blocksBucket = "blocks" 12 | 13 | // tip 这个词本身有事物尖端或尾部的意思,这里指的是存储最后一个块的哈希 14 | // 在链的末端可能出现短暂分叉的情况,所以选择 tip 其实也就是选择了哪条链 15 | // db 存储数据库连接 16 | type Blockchain struct { 17 | tip []byte 18 | db *bolt.DB 19 | } 20 | 21 | func NewBlockchain() *Blockchain { 22 | var tip []byte 23 | // 打开一个 BoltDB 文件 24 | db, err := bolt.Open(dbFile, 0600, nil) 25 | if err != nil { 26 | log.Panic(err) 27 | } 28 | 29 | err = db.Update(func(tx *bolt.Tx) error { 30 | b := tx.Bucket([]byte(blocksBucket)) 31 | 32 | // 如果数据库中不存在区块链就创建一个,否则直接读取最后一个块的哈希 33 | if b == nil { 34 | fmt.Println("No existing blockchain found. Creating a new one...") 35 | genesis := NewGenesisBlock() 36 | 37 | b, err := tx.CreateBucket([]byte(blocksBucket)) 38 | if err != nil { 39 | log.Panic(err) 40 | } 41 | 42 | err = b.Put(genesis.Hash, genesis.Serialize()) 43 | if err != nil { 44 | log.Panic(err) 45 | } 46 | 47 | err = b.Put([]byte("l"), genesis.Hash) 48 | if err != nil { 49 | log.Panic(err) 50 | } 51 | tip = genesis.Hash 52 | } else { 53 | tip = b.Get([]byte("l")) 54 | } 55 | 56 | return nil 57 | }) 58 | 59 | if err != nil { 60 | log.Panic(err) 61 | } 62 | 63 | bc := Blockchain{tip, db} 64 | 65 | return &bc 66 | } 67 | 68 | // 加入区块时,需要将区块持久化到数据库中 69 | func (bc *Blockchain) AddBlock(data string) { 70 | var lastHash []byte 71 | 72 | // 首先获取最后一个块的哈希用于生成新块的哈希 73 | err := bc.db.View(func(tx *bolt.Tx) error { 74 | b := tx.Bucket([]byte(blocksBucket)) 75 | lastHash = b.Get([]byte("l")) 76 | 77 | return nil 78 | }) 79 | 80 | if err != nil { 81 | log.Panic(err) 82 | } 83 | 84 | newBlock := NewBlock(data, lastHash) 85 | 86 | err = bc.db.Update(func(tx *bolt.Tx) error { 87 | b := tx.Bucket([]byte(blocksBucket)) 88 | err := b.Put(newBlock.Hash, newBlock.Serialize()) 89 | if err != nil { 90 | log.Panic(err) 91 | } 92 | 93 | err = b.Put([]byte("l"), newBlock.Hash) 94 | if err != nil { 95 | log.Panic(err) 96 | } 97 | 98 | bc.tip = newBlock.Hash 99 | 100 | return nil 101 | }) 102 | } 103 | 104 | type BlockchainIterator struct { 105 | currentHash []byte 106 | db *bolt.DB 107 | } 108 | 109 | func (bc *Blockchain) Iterator() *BlockchainIterator { 110 | bci := &BlockchainIterator{bc.tip, bc.db} 111 | 112 | return bci 113 | } 114 | 115 | // 返回链中的下一个块 116 | func (i *BlockchainIterator) Next() *Block { 117 | var block *Block 118 | 119 | err := i.db.View(func(tx *bolt.Tx) error { 120 | b := tx.Bucket([]byte(blocksBucket)) 121 | encodedBlock := b.Get(i.currentHash) 122 | block = DeserializeBlock(encodedBlock) 123 | 124 | return nil 125 | }) 126 | 127 | if err != nil { 128 | log.Panic(err) 129 | } 130 | 131 | i.currentHash = block.PrevBlockHash 132 | 133 | return block 134 | } 135 | -------------------------------------------------------------------------------- /content/part-3/src/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | type CLI struct { 11 | bc *Blockchain 12 | } 13 | 14 | const usage = ` 15 | Usage: 16 | addblock -data BLOCK_DATA add a block to the blockchain 17 | printchain print all the blocks of the blockchain 18 | ` 19 | 20 | func (cli *CLI) printUsage() { 21 | fmt.Println(usage) 22 | } 23 | 24 | func (cli *CLI) validateArgs() { 25 | if len(os.Args) < 2 { 26 | cli.printUsage() 27 | os.Exit(1) 28 | } 29 | } 30 | 31 | func (cli *CLI) Run() { 32 | cli.validateArgs() 33 | 34 | addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) 35 | printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 36 | 37 | addBlockData := addBlockCmd.String("data", "", "Block data") 38 | 39 | switch os.Args[1] { 40 | case "addblock": 41 | err := addBlockCmd.Parse(os.Args[2:]) 42 | if err != nil { 43 | log.Panic(err) 44 | } 45 | case "printchain": 46 | err := printChainCmd.Parse(os.Args[2:]) 47 | if err != nil { 48 | log.Panic(err) 49 | } 50 | default: 51 | cli.printUsage() 52 | os.Exit(1) 53 | } 54 | 55 | if addBlockCmd.Parsed() { 56 | if *addBlockData == "" { 57 | addBlockCmd.Usage() 58 | os.Exit(1) 59 | } 60 | cli.bc.AddBlock(*addBlockData) 61 | } 62 | 63 | if printChainCmd.Parsed() { 64 | cli.printChain() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /content/part-3/src/commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | func (cli *CLI) addBlock(data string) { 9 | cli.bc.AddBlock(data) 10 | fmt.Println("Success") 11 | } 12 | 13 | func (cli *CLI) printChain() { 14 | bci := cli.bc.Iterator() 15 | 16 | for { 17 | block := bci.Next() 18 | 19 | fmt.Printf("Prev hash: %x\n", block.PrevBlockHash) 20 | fmt.Printf("Data: %s\n", block.Data) 21 | fmt.Printf("Hash: %x\n", block.Hash) 22 | pow := NewProofOfWork(block) 23 | fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 24 | fmt.Println() 25 | 26 | if len(block.PrevBlockHash) == 0 { 27 | break 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /content/part-3/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | bc := NewBlockchain() 5 | defer bc.db.Close() 6 | 7 | cli := CLI{bc} 8 | cli.Run() 9 | } 10 | -------------------------------------------------------------------------------- /content/part-3/src/proofofwork.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | ) 10 | 11 | const targetBits = 24 12 | 13 | var ( 14 | maxNonce = math.MaxInt64 15 | ) 16 | 17 | type ProofOfWork struct { 18 | block *Block 19 | target *big.Int 20 | } 21 | 22 | func NewProofOfWork(b *Block) *ProofOfWork { 23 | target := big.NewInt(1) 24 | target.Lsh(target, uint(256-targetBits)) 25 | 26 | pow := &ProofOfWork{b, target} 27 | 28 | return pow 29 | } 30 | 31 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 32 | data := bytes.Join( 33 | [][]byte{ 34 | pow.block.PrevBlockHash, 35 | pow.block.Data, 36 | IntToHex(pow.block.Timestamp), 37 | IntToHex(int64(targetBits)), 38 | IntToHex(int64(nonce)), 39 | }, 40 | []byte{}, 41 | ) 42 | 43 | return data 44 | } 45 | 46 | func (pow *ProofOfWork) Run() (int, []byte) { 47 | var hashInt big.Int 48 | var hash [32]byte 49 | nonce := 0 50 | 51 | fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) 52 | for nonce < maxNonce { 53 | data := pow.prepareData(nonce) 54 | 55 | hash = sha256.Sum256(data) 56 | hashInt.SetBytes(hash[:]) 57 | 58 | if hashInt.Cmp(pow.target) == -1 { 59 | fmt.Printf("\r%x", hash) 60 | break 61 | } else { 62 | nonce++ 63 | } 64 | } 65 | fmt.Print("\n\n") 66 | 67 | return nonce, hash[:] 68 | } 69 | 70 | // Validate block's PoW 71 | func (pow *ProofOfWork) Validate() bool { 72 | var hashInt big.Int 73 | 74 | data := pow.prepareData(pow.block.Nonce) 75 | hash := sha256.Sum256(data) 76 | hashInt.SetBytes(hash[:]) 77 | 78 | isValid := hashInt.Cmp(pow.target) == -1 79 | 80 | return isValid 81 | } 82 | -------------------------------------------------------------------------------- /content/part-3/src/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "log" 7 | ) 8 | 9 | // Convert an int64 to a byte array 10 | func IntToHex(num int64) []byte { 11 | buff := new(bytes.Buffer) 12 | err := binary.Write(buff, binary.BigEndian, num) 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | 17 | return buff.Bytes() 18 | } 19 | -------------------------------------------------------------------------------- /content/part-4/img/blockchain-info-tx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/blockchain-tutorial/a9d16df68906a78934093d90954afa9bf771688f/content/part-4/img/blockchain-info-tx.png -------------------------------------------------------------------------------- /content/part-4/src/Makefile: -------------------------------------------------------------------------------- 1 | BINARY := blockchain 2 | 3 | build: 4 | @echo "====> Go build" 5 | @go build -o $(BINARY) 6 | 7 | .PHONY: build 8 | -------------------------------------------------------------------------------- /content/part-4/src/block.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/gob" 7 | "log" 8 | "time" 9 | ) 10 | 11 | // 时间戳 12 | // 交易 13 | // 前一个块哈希 14 | // 当前块哈希 15 | // 难度值 16 | type Block struct { 17 | Timestamp int64 18 | Transactions []*Transaction 19 | PrevBlockHash []byte 20 | Hash []byte 21 | Nonce int 22 | } 23 | 24 | func (b *Block) Serialize() []byte { 25 | var result bytes.Buffer 26 | encoder := gob.NewEncoder(&result) 27 | 28 | err := encoder.Encode(b) 29 | if err != nil { 30 | log.Panic(err) 31 | } 32 | 33 | return result.Bytes() 34 | } 35 | 36 | // Deserialize a block 37 | func DeserializeBlock(d []byte) *Block { 38 | var block Block 39 | 40 | decoder := gob.NewDecoder(bytes.NewReader(d)) 41 | err := decoder.Decode(&block) 42 | if err != nil { 43 | log.Panic(err) 44 | } 45 | 46 | return &block 47 | } 48 | 49 | // NewBlock creates and returns Block 50 | func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { 51 | block := &Block{ 52 | Timestamp: time.Now().Unix(), 53 | Transactions: transactions, 54 | PrevBlockHash: prevBlockHash, 55 | Hash: []byte{}, 56 | Nonce: 0} 57 | pow := NewProofOfWork(block) 58 | nonce, hash := pow.Run() 59 | 60 | block.Hash = hash[:] 61 | block.Nonce = nonce 62 | 63 | return block 64 | } 65 | 66 | // NewGenesisBlock creates and returns Genesis Block 67 | func NewGenesisBlock(coinbase *Transaction) *Block { 68 | return NewBlock([]*Transaction{coinbase}, []byte{}) 69 | } 70 | 71 | // 计算区块里所有交易的哈希 72 | func (b *Block) HashTransactions() []byte { 73 | var txHashes [][]byte 74 | var txHash [32]byte 75 | 76 | for _, tx := range b.Transactions { 77 | txHashes = append(txHashes, tx.ID) 78 | } 79 | txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) 80 | 81 | return txHash[:] 82 | } 83 | -------------------------------------------------------------------------------- /content/part-4/src/blockchain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/boltdb/bolt" 10 | ) 11 | 12 | const dbFile = "blockchain.db" 13 | const blocksBucket = "blocks" 14 | const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" 15 | 16 | type Blockchain struct { 17 | tip []byte 18 | db *bolt.DB 19 | } 20 | 21 | type BlockchainIterator struct { 22 | currentHash []byte 23 | db *bolt.DB 24 | } 25 | 26 | func (bc *Blockchain) MineBlock(transactions []*Transaction) { 27 | var lastHash []byte 28 | 29 | err := bc.db.View(func(tx *bolt.Tx) error { 30 | b := tx.Bucket([]byte(blocksBucket)) 31 | lastHash = b.Get([]byte("l")) 32 | 33 | return nil 34 | }) 35 | 36 | if err != nil { 37 | log.Panic(err) 38 | } 39 | 40 | newBlock := NewBlock(transactions, lastHash) 41 | 42 | err = bc.db.Update(func(tx *bolt.Tx) error { 43 | b := tx.Bucket([]byte(blocksBucket)) 44 | err := b.Put(newBlock.Hash, newBlock.Serialize()) 45 | if err != nil { 46 | log.Panic(err) 47 | } 48 | 49 | err = b.Put([]byte("l"), newBlock.Hash) 50 | if err != nil { 51 | log.Panic(err) 52 | } 53 | 54 | bc.tip = newBlock.Hash 55 | 56 | return nil 57 | }) 58 | } 59 | 60 | func (bc *Blockchain) Iterator() *BlockchainIterator { 61 | bci := &BlockchainIterator{bc.tip, bc.db} 62 | 63 | return bci 64 | } 65 | 66 | func (i *BlockchainIterator) Next() *Block { 67 | var block *Block 68 | 69 | err := i.db.View(func(tx *bolt.Tx) error { 70 | b := tx.Bucket([]byte(blocksBucket)) 71 | encodedBlock := b.Get(i.currentHash) 72 | block = DeserializeBlock(encodedBlock) 73 | 74 | return nil 75 | }) 76 | 77 | if err != nil { 78 | log.Panic(err) 79 | } 80 | 81 | i.currentHash = block.PrevBlockHash 82 | 83 | return block 84 | } 85 | 86 | func dbExists() bool { 87 | if _, err := os.Stat(dbFile); os.IsNotExist(err) { 88 | return false 89 | } 90 | 91 | return true 92 | } 93 | 94 | // 创建一个有创世块的新链 95 | func NewBlockchain(address string) *Blockchain { 96 | if dbExists() == false { 97 | fmt.Println("No existing blockchain found. Create one first.") 98 | os.Exit(1) 99 | } 100 | var tip []byte 101 | db, err := bolt.Open(dbFile, 0600, nil) 102 | if err != nil { 103 | log.Panic(err) 104 | } 105 | 106 | err = db.Update(func(tx *bolt.Tx) error { 107 | b := tx.Bucket([]byte(blocksBucket)) 108 | tip = b.Get([]byte("l")) 109 | 110 | return nil 111 | }) 112 | 113 | if err != nil { 114 | log.Panic(err) 115 | } 116 | 117 | bc := Blockchain{tip, db} 118 | 119 | return &bc 120 | } 121 | 122 | // CreateBlockchain 创建一个新的区块链数据库 123 | // address 用来接收挖出创世块的奖励 124 | func CreateBlockchain(address string) *Blockchain { 125 | if dbExists() { 126 | fmt.Println("Blockchain already exists.") 127 | os.Exit(1) 128 | } 129 | 130 | var tip []byte 131 | db, err := bolt.Open(dbFile, 0600, nil) 132 | if err != nil { 133 | log.Panic(err) 134 | } 135 | 136 | err = db.Update(func(tx *bolt.Tx) error { 137 | cbtx := NewCoinbaseTX(address, genesisCoinbaseData) 138 | genesis := NewGenesisBlock(cbtx) 139 | 140 | b, err := tx.CreateBucket([]byte(blocksBucket)) 141 | if err != nil { 142 | log.Panic(err) 143 | } 144 | 145 | err = b.Put(genesis.Hash, genesis.Serialize()) 146 | if err != nil { 147 | log.Panic(err) 148 | } 149 | 150 | err = b.Put([]byte("l"), genesis.Hash) 151 | if err != nil { 152 | log.Panic(err) 153 | } 154 | tip = genesis.Hash 155 | 156 | return nil 157 | }) 158 | 159 | if err != nil { 160 | log.Panic(err) 161 | } 162 | 163 | bc := Blockchain{tip, db} 164 | 165 | return &bc 166 | } 167 | 168 | // FindUnspentTransactions 找到未花费输出的交易 169 | func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { 170 | var unspentTXs []Transaction 171 | spentTXOs := make(map[string][]int) 172 | bci := bc.Iterator() 173 | 174 | for { 175 | block := bci.Next() 176 | 177 | for _, tx := range block.Transactions { 178 | txID := hex.EncodeToString(tx.ID) 179 | 180 | Outputs: 181 | for outIdx, out := range tx.Vout { 182 | // 如果交易输出被花费了 183 | if spentTXOs[txID] != nil { 184 | for _, spentOut := range spentTXOs[txID] { 185 | if spentOut == outIdx { 186 | continue Outputs 187 | } 188 | } 189 | } 190 | 191 | // 如果该交易输出可以被解锁,即可被花费 192 | if out.CanBeUnlockedWith(address) { 193 | unspentTXs = append(unspentTXs, *tx) 194 | } 195 | } 196 | 197 | if tx.IsCoinbase() == false { 198 | for _, in := range tx.Vin { 199 | if in.CanUnlockOutputWith(address) { 200 | inTxID := hex.EncodeToString(in.Txid) 201 | spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) 202 | } 203 | } 204 | } 205 | } 206 | 207 | if len(block.PrevBlockHash) == 0 { 208 | break 209 | } 210 | } 211 | 212 | return unspentTXs 213 | } 214 | 215 | func (bc *Blockchain) FindUTXO(address string) []TXOutput { 216 | var UTXOs []TXOutput 217 | unspentTransactions := bc.FindUnspentTransactions(address) 218 | 219 | for _, tx := range unspentTransactions { 220 | for _, out := range tx.Vout { 221 | if out.CanBeUnlockedWith(address) { 222 | UTXOs = append(UTXOs, out) 223 | } 224 | } 225 | } 226 | 227 | return UTXOs 228 | } 229 | 230 | // FindSpendableOutputs 从 address 中找到至少 amount 的 UTXO 231 | func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { 232 | unspentOutputs := make(map[string][]int) 233 | unspentTXs := bc.FindUnspentTransactions(address) 234 | accumulated := 0 235 | 236 | Work: 237 | for _, tx := range unspentTXs { 238 | txID := hex.EncodeToString(tx.ID) 239 | 240 | for outIdx, out := range tx.Vout { 241 | if out.CanBeUnlockedWith(address) && accumulated < amount { 242 | accumulated += out.Value 243 | unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) 244 | 245 | if accumulated >= amount { 246 | break Work 247 | } 248 | } 249 | } 250 | } 251 | 252 | return accumulated, unspentOutputs 253 | } 254 | -------------------------------------------------------------------------------- /content/part-4/src/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | type CLI struct{} 12 | 13 | func (cli *CLI) createBlockchain(address string) { 14 | bc := CreateBlockchain(address) 15 | bc.db.Close() 16 | fmt.Println("Done!") 17 | } 18 | 19 | func (cli *CLI) getBalance(address string) { 20 | bc := NewBlockchain(address) 21 | defer bc.db.Close() 22 | 23 | balance := 0 24 | UTXOs := bc.FindUTXO(address) 25 | 26 | for _, out := range UTXOs { 27 | balance += out.Value 28 | } 29 | 30 | fmt.Printf("Balance of '%s': %d\n", address, balance) 31 | } 32 | 33 | func (cli *CLI) printUsage() { 34 | fmt.Println("Usage:") 35 | fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") 36 | fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS") 37 | fmt.Println(" printchain - Print all the blocks of the blockchain") 38 | fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") 39 | } 40 | 41 | func (cli *CLI) validateArgs() { 42 | if len(os.Args) < 2 { 43 | cli.printUsage() 44 | os.Exit(1) 45 | } 46 | } 47 | 48 | func (cli *CLI) printChain() { 49 | bc := NewBlockchain("") 50 | defer bc.db.Close() 51 | 52 | bci := bc.Iterator() 53 | 54 | for { 55 | block := bci.Next() 56 | 57 | fmt.Printf("Prev hash: %x\n", block.PrevBlockHash) 58 | fmt.Printf("Hash: %x\n", block.Hash) 59 | pow := NewProofOfWork(block) 60 | fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) 61 | fmt.Println() 62 | 63 | if len(block.PrevBlockHash) == 0 { 64 | break 65 | } 66 | } 67 | } 68 | 69 | func (cli *CLI) send(from, to string, amount int) { 70 | bc := NewBlockchain(from) 71 | defer bc.db.Close() 72 | 73 | tx := NewUTXOTransaction(from, to, amount, bc) 74 | bc.MineBlock([]*Transaction{tx}) 75 | fmt.Println("Success!") 76 | } 77 | 78 | func (cli *CLI) Run() { 79 | cli.validateArgs() 80 | 81 | getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) 82 | createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) 83 | sendCmd := flag.NewFlagSet("send", flag.ExitOnError) 84 | printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 85 | 86 | getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") 87 | createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") 88 | sendFrom := sendCmd.String("from", "", "Source wallet address") 89 | sendTo := sendCmd.String("to", "", "Destination wallet address") 90 | sendAmount := sendCmd.Int("amount", 0, "Amount to send") 91 | 92 | switch os.Args[1] { 93 | case "getbalance": 94 | err := getBalanceCmd.Parse(os.Args[2:]) 95 | if err != nil { 96 | log.Panic(err) 97 | } 98 | case "createblockchain": 99 | err := createBlockchainCmd.Parse(os.Args[2:]) 100 | if err != nil { 101 | log.Panic(err) 102 | } 103 | case "send": 104 | err := sendCmd.Parse(os.Args[2:]) 105 | if err != nil { 106 | log.Panic(err) 107 | } 108 | case "printchain": 109 | err := printChainCmd.Parse(os.Args[2:]) 110 | if err != nil { 111 | log.Panic(err) 112 | } 113 | default: 114 | cli.printUsage() 115 | os.Exit(1) 116 | } 117 | 118 | if getBalanceCmd.Parsed() { 119 | if *getBalanceAddress == "" { 120 | getBalanceCmd.Usage() 121 | os.Exit(1) 122 | } 123 | cli.getBalance(*getBalanceAddress) 124 | } 125 | 126 | if createBlockchainCmd.Parsed() { 127 | if *createBlockchainAddress == "" { 128 | createBlockchainCmd.Usage() 129 | os.Exit(1) 130 | } 131 | cli.createBlockchain(*createBlockchainAddress) 132 | } 133 | 134 | if printChainCmd.Parsed() { 135 | cli.printChain() 136 | } 137 | 138 | if sendCmd.Parsed() { 139 | if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { 140 | sendCmd.Usage() 141 | os.Exit(1) 142 | } 143 | 144 | cli.send(*sendFrom, *sendTo, *sendAmount) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /content/part-4/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | cli := CLI{} 5 | cli.Run() 6 | } 7 | -------------------------------------------------------------------------------- /content/part-4/src/proofofwork.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | ) 10 | 11 | const targetBits = 24 12 | 13 | var ( 14 | maxNonce = math.MaxInt64 15 | ) 16 | 17 | type ProofOfWork struct { 18 | block *Block 19 | target *big.Int 20 | } 21 | 22 | func NewProofOfWork(b *Block) *ProofOfWork { 23 | target := big.NewInt(1) 24 | target.Lsh(target, uint(256-targetBits)) 25 | 26 | pow := &ProofOfWork{b, target} 27 | 28 | return pow 29 | } 30 | 31 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 32 | data := bytes.Join( 33 | [][]byte{ 34 | pow.block.PrevBlockHash, 35 | pow.block.HashTransactions(), 36 | IntToHex(pow.block.Timestamp), 37 | IntToHex(int64(targetBits)), 38 | IntToHex(int64(nonce)), 39 | }, 40 | []byte{}, 41 | ) 42 | 43 | return data 44 | } 45 | 46 | func (pow *ProofOfWork) Run() (int, []byte) { 47 | var hashInt big.Int 48 | var hash [32]byte 49 | nonce := 0 50 | 51 | fmt.Printf("Mining a new block") 52 | for nonce < maxNonce { 53 | data := pow.prepareData(nonce) 54 | 55 | hash = sha256.Sum256(data) 56 | hashInt.SetBytes(hash[:]) 57 | 58 | if hashInt.Cmp(pow.target) == -1 { 59 | fmt.Printf("\r%x", hash) 60 | break 61 | } else { 62 | nonce++ 63 | } 64 | } 65 | fmt.Print("\n\n") 66 | 67 | return nonce, hash[:] 68 | } 69 | 70 | // Validate block's PoW 71 | func (pow *ProofOfWork) Validate() bool { 72 | var hashInt big.Int 73 | 74 | data := pow.prepareData(pow.block.Nonce) 75 | hash := sha256.Sum256(data) 76 | hashInt.SetBytes(hash[:]) 77 | 78 | isValid := hashInt.Cmp(pow.target) == -1 79 | 80 | return isValid 81 | } 82 | -------------------------------------------------------------------------------- /content/part-4/src/transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/gob" 7 | "encoding/hex" 8 | "fmt" 9 | "log" 10 | ) 11 | 12 | const subsidy = 10 13 | 14 | // Transaction 由交易 ID,输入和输出构成 15 | type Transaction struct { 16 | ID []byte 17 | Vin []TXInput 18 | Vout []TXOutput 19 | } 20 | 21 | // TXInput 包含 3 部分 22 | // Txid: 一个交易输入引用了之前一笔交易的一个输出, ID 表明是之前哪笔交易 23 | // Vout: 一笔交易可能有多个输出,Vout 为输出的索引 24 | // ScriptSig: 提供解锁输出 Txid:Vout 的数据 25 | type TXInput struct { 26 | Txid []byte 27 | Vout int 28 | ScriptSig string 29 | } 30 | 31 | // TXOutput 包含两部分 32 | // Value: 有多少币,就是存储在 Value 里面 33 | // ScriptPubKey: 对输出进行锁定 34 | // 在当前实现中,ScriptPubKey 将仅用一个字符串来代替 35 | type TXOutput struct { 36 | Value int 37 | ScriptPubKey string 38 | } 39 | 40 | // IsCoinbase 判断是否是 coinbase 交易 41 | func (tx Transaction) IsCoinbase() bool { 42 | return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 43 | } 44 | 45 | func (tx *Transaction) SetID() { 46 | var encoded bytes.Buffer 47 | var hash [32]byte 48 | 49 | enc := gob.NewEncoder(&encoded) 50 | err := enc.Encode(tx) 51 | if err != nil { 52 | log.Panic(err) 53 | } 54 | hash = sha256.Sum256(encoded.Bytes()) 55 | tx.ID = hash[:] 56 | } 57 | 58 | // 这里的 unlockingData 可以理解为地址 59 | func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { 60 | return in.ScriptSig == unlockingData 61 | } 62 | 63 | func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { 64 | return out.ScriptPubKey == unlockingData 65 | } 66 | 67 | // NewCoinbaseTX 构建 coinbase 交易,该没有输入,只有一个输出 68 | func NewCoinbaseTX(to, data string) *Transaction { 69 | if data == "" { 70 | data = fmt.Sprintf("Reward to '%s'", to) 71 | } 72 | 73 | txin := TXInput{[]byte{}, -1, data} 74 | txout := TXOutput{subsidy, to} 75 | tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} 76 | tx.SetID() 77 | 78 | return &tx 79 | } 80 | 81 | // NewUTXOTransaction 创建一笔新的交易 82 | func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { 83 | var inputs []TXInput 84 | var outputs []TXOutput 85 | 86 | // 找到足够的未花费输出 87 | acc, validOutputs := bc.FindSpendableOutputs(from, amount) 88 | 89 | if acc < amount { 90 | log.Panic("ERROR: Not enough funds") 91 | } 92 | 93 | for txid, outs := range validOutputs { 94 | txID, err := hex.DecodeString(txid) 95 | if err != nil { 96 | log.Panic(err) 97 | } 98 | 99 | for _, out := range outs { 100 | input := TXInput{txID, out, from} 101 | inputs = append(inputs, input) 102 | } 103 | } 104 | 105 | outputs = append(outputs, TXOutput{amount, to}) 106 | 107 | // 如果 UTXO 总数超过所需,则产生找零 108 | if acc > amount { 109 | outputs = append(outputs, TXOutput{acc - amount, from}) 110 | } 111 | 112 | tx := Transaction{nil, inputs, outputs} 113 | tx.SetID() 114 | 115 | return &tx 116 | } 117 | -------------------------------------------------------------------------------- /content/part-4/src/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "log" 7 | ) 8 | 9 | // Convert an int64 to a byte array 10 | func IntToHex(num int64) []byte { 11 | buff := new(bytes.Buffer) 12 | err := binary.Write(buff, binary.BigEndian, num) 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | 17 | return buff.Bytes() 18 | } 19 | -------------------------------------------------------------------------------- /content/part-4/transactions-1.md: -------------------------------------------------------------------------------- 1 | 交易(1) 2 | ======== 3 | 4 | ## 引言 5 | 6 | 交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。今天,我们将会开始实现交易。不过,由于交易是很大的话题,我会把它分为两部分来讲:在今天这个部分,我们会实现交易的基本框架。在第二部分,我们会继续讨论它的一些细节。 7 | 8 | 由于比特币采用的是 UTXO 模型,并非账户模型,并不直接存在“余额”这个概念,余额需要通过遍历整个交易历史得来。 9 | 10 | ## 比特币交易 11 | 12 | 点击 [这里](https://blockchain.info/zh-cn/tx/b6f6b339b546a13822192b06ccbdd817afea5311845f769702ae2912f7d94ab5) 在 blockchain.info 查看下图中的交易信息。 13 | 14 | ![tx](./img/blockchain-info-tx.png) 15 | 16 | 一笔交易由一些输入(input)和输出(output)组合而来: 17 | 18 | ```go 19 | type Transaction struct { 20 | ID []byte 21 | Vin []TXInput 22 | Vout []TXOutput 23 | } 24 | ``` 25 | 26 | 对于每一笔新的交易,它的输入会引用(reference)之前一笔交易的输出(这里有个例外,coinbase 交易),引用就是花费的意思。所谓引用之前的一个输出,也就是将之前的一个输出包含在另一笔交易的输入当中,就是花费之前的交易输出。交易的输出,就是币实际存储的地方。下面的图示阐释了交易之间的互相关联: 27 | 28 | ![the interconnection of transactions](../images/127313-4f8e668c826fd31a.png) 29 | 30 | 注意: 31 | 32 | 1. 有一些输出并没有被关联到某个输入上 33 | 2. 一笔交易的输入可以引用之前多笔交易的输出 34 | 3. 一个输入必须引用一个输出 35 | 36 | 贯穿本文,我们将会使用像“钱(money)”,“币(coin)”,“花费(spend)”,“发送(send)”,“账户(account)” 等等这样的词。但是在比特币中,其实并不存在这样的概念。交易仅仅是通过一个脚本(script)来锁定(lock)一些值(value),而这些值只可以被锁定它们的人解锁(unlock)。 37 | 38 | 每一笔比特币交易都会创造输出,输出都会被区块链记录下来。给某个人发送比特币,实际上意味着创造新的 UTXO 并注册到那个人的地址,可以为他所用。 39 | 40 | ## 交易输出 41 | 42 | 先从输出(output)开始: 43 | 44 | ```go 45 | type TXOutput struct { 46 | Value int 47 | ScriptPubKey string 48 | } 49 | ``` 50 | 51 | 输出主要包含两部分: 52 | 53 | 1. 一定量的比特币(`Value`) 54 | 2. 一个锁定脚本(`ScriptPubKey`),要花这笔钱,必须要解锁该脚本。 55 | 56 | 实际上,正是输出里面存储了“币”(注意,也就是上面的 `Value` 字段)。而这里的存储,指的是用一个数学难题对输出进行锁定,这个难题被存储在 `ScriptPubKey` 里面。在内部,比特币使用了一个叫做 *Script* 的脚本语言,用它来定义锁定和解锁输出的逻辑。虽然这个语言相当的原始(这是为了避免潜在的黑客攻击和滥用而有意为之),并不复杂,但是我们也并不会在这里讨论它的细节。你可以在[这里](https://en.bitcoin.it/wiki/Script) 找到详细解释。 57 | 58 | >在比特币中,`value` 字段存储的是 *satoshi* 的数量,而不是 BTC 的数量。一个 *satoshi* 等于一亿分之一的 BTC(0.00000001 BTC),这也是比特币里面最小的货币单位(就像是 1 分的硬币)。 59 | 60 | 由于还没有实现地址(address),所以目前我们会避免涉及逻辑相关的完整脚本。`ScriptPubKey` 将会存储一个任意的字符串(用户定义的钱包地址)。 61 | 62 | >顺便说一下,有了一个这样的脚本语言,也意味着比特币其实也可以作为一个智能合约平台。 63 | 64 | 关于输出,非常重要的一点是:它们是**不可再分的(indivisible)**。也就是说,你无法仅引用它的其中某一部分。要么不用,如果要用,必须一次性用完。当一个新的交易中引用了某个输出,那么这个输出必须被全部花费。如果它的值比需要的值大,那么就会产生一个找零,找零会返还给发送方。这跟现实世界的场景十分类似,当你想要支付的时候,如果一个东西值 1 美元,而你给了一个 5 美元的纸币,那么你会得到一个 4 美元的找零。 65 | 66 | ## 交易输入 67 | 68 | 这里是输入: 69 | 70 | ```go 71 | type TXInput struct { 72 | Txid []byte 73 | Vout int 74 | ScriptSig string 75 | } 76 | ``` 77 | 78 | 正如之前所提到的,一个输入引用了之前交易的一个输出:`Txid` 存储的是之前交易的 ID,`Vout` 存储的是该输出在那笔交易中所有输出的索引(因为一笔交易可能有多个输出,需要有信息指明是具体的哪一个)。`ScriptSig` 是一个脚本,提供了可解锁输出结构里面 `ScriptPubKey` 字段的数据。如果 `ScriptSig` 提供的数据是正确的,那么输出就会被解锁,然后被解锁的值就可以被用于产生新的输出;如果数据不正确,输出就无法被引用在输入中,或者说,无法使用这个输出。这种机制,保证了用户无法花费属于其他人的币。 79 | 80 | 再次强调,由于我们还没有实现地址,所以目前 `ScriptSig` 将仅仅存储一个用户自定义的任意钱包地址。我们会在下一篇文章中实现公钥(public key)和签名(signature)。 81 | 82 | 来简要总结一下。输出,就是 “币” 存储的地方。每个输出都会带有一个解锁脚本,这个脚本定义了解锁该输出的逻辑。每笔新的交易,必须至少有一个输入和输出。一个输入引用了之前一笔交易的输出,并提供了解锁数据(也就是 `ScriptSig` 字段),该数据会被用在输出的解锁脚本中解锁输出,解锁完成后即可使用它的值去产生新的输出。 83 | 84 | 每一笔输入都是之前一笔交易的输出,那么假设从某一笔交易开始不断往前追溯,它所涉及的输入和输出到底是谁先存在呢?换个说法,这是个鸡和蛋谁先谁后的问题,是先有蛋还是先有鸡呢? 85 | 86 | ## 先有蛋 87 | 88 | 在比特币中,是先有蛋,然后才有鸡。输入引用输出的逻辑,是经典的“蛋还是鸡”问题:输入先产生输出,然后输出使得输入成为可能。在比特币中,最先有输出,然后才有输入。换而言之,第一笔交易只有输出,没有输入。 89 | 90 | 当矿工挖出一个新的块时,它会向新的块中添加一个 **coinbase** 交易。coinbase 交易是一种特殊的交易,它不需要引用之前一笔交易的输出。它“凭空”产生了币(也就是产生了新币),这是矿工获得挖出新块的奖励,也可以理解为“发行新币”。 91 | 92 | 在区块链的最初,也就是第一个块,叫做创世块。正是这个创世块,产生了区块链最开始的输出。对于创世块,不需要引用之前的交易输出。因为在创世块之前根本不存在交易,也就没有不存在交易输出。 93 | 94 | 来创建一个 coinbase 交易: 95 | 96 | ```go 97 | func NewCoinbaseTX(to, data string) *Transaction { 98 | if data == "" { 99 | data = fmt.Sprintf("Reward to '%s'", to) 100 | } 101 | 102 | txin := TXInput{[]byte{}, -1, data} 103 | txout := TXOutput{subsidy, to} 104 | tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} 105 | tx.SetID() 106 | 107 | return &tx 108 | } 109 | ``` 110 | 111 | coinbase 交易只有一个输出,没有输入。在我们的实现中,它表现为 `Txid` 为空,`Vout` 等于 -1。并且,在当前实现中,coinbase 交易也没有在 `ScriptSig` 中存储脚本,而只是存储了一个任意的字符串 `data`。 112 | 113 | >在比特币中,第一笔 coinbase 交易包含了如下信息:“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”。[可点击这里查看](https://blockchain.info/tx/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b?show_adv=true). 114 | 115 | `subsidy` 是挖出新块的奖励金。在比特币中,实际并没有存储这个数字,而是基于区块总数进行计算而得:区块总数除以 210000 就是 `subsidy`。挖出创世块的奖励是 50 BTC,每挖出 `210000` 个块后,奖励减半。在我们的实现中,这个奖励值将会是一个常量(至少目前是)。 116 | 117 | ## 将交易保存到区块链 118 | 119 | 从现在开始,每个块必须存储至少一笔交易。如果没有交易,也就不可能出新的块。这意味着我们应该移除 `Block` 的 `Data` 字段,取而代之的是存储交易: 120 | 121 | ```go 122 | type Block struct { 123 | Timestamp int64 124 | Transactions []*Transaction 125 | PrevBlockHash []byte 126 | Hash []byte 127 | Nonce int 128 | } 129 | ``` 130 | 131 | `NewBlock` 和 `NewGenesisBlock` 也必须做出相应改变: 132 | 133 | ```go 134 | func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { 135 | block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} 136 | ... 137 | } 138 | 139 | func NewGenesisBlock(coinbase *Transaction) *Block { 140 | return NewBlock([]*Transaction{coinbase}, []byte{}) 141 | } 142 | ``` 143 | 144 | 接下来修改创建区块链的函数: 145 | 146 | ```go 147 | func CreateBlockchain(address string) *Blockchain { 148 | ... 149 | err = db.Update(func(tx *bolt.Tx) error { 150 | cbtx := NewCoinbaseTX(address, genesisCoinbaseData) 151 | genesis := NewGenesisBlock(cbtx) 152 | 153 | b, err := tx.CreateBucket([]byte(blocksBucket)) 154 | err = b.Put(genesis.Hash, genesis.Serialize()) 155 | ... 156 | }) 157 | ... 158 | } 159 | ``` 160 | 161 | 现在,这个函数会接受一个地址作为参数,这个地址将会被用来接收挖出创世块的奖励。 162 | 163 | ## 工作量证明 164 | 165 | 工作量证明算法必须要将存储在区块里面的交易考虑进去,从而保证区块链交易存储的一致性和可靠性。所以,我们必须修改 `ProofOfWork.prepareData` 方法: 166 | 167 | ```go 168 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 169 | data := bytes.Join( 170 | [][]byte{ 171 | pow.block.PrevBlockHash, 172 | pow.block.HashTransactions(), // This line was changed 173 | IntToHex(pow.block.Timestamp), 174 | IntToHex(int64(targetBits)), 175 | IntToHex(int64(nonce)), 176 | }, 177 | []byte{}, 178 | ) 179 | 180 | return data 181 | } 182 | ``` 183 | 184 | 不像之前使用 `pow.block.Data`,现在我们使用 `pow.block.HashTransactions()` : 185 | 186 | ```go 187 | func (b *Block) HashTransactions() []byte { 188 | var txHashes [][]byte 189 | var txHash [32]byte 190 | 191 | for _, tx := range b.Transactions { 192 | txHashes = append(txHashes, tx.ID) 193 | } 194 | txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) 195 | 196 | return txHash[:] 197 | } 198 | ``` 199 | 200 | 通过哈希提供数据的唯一表示,这种做法我们已经不是第一次遇到了。我们想要通过仅仅一个哈希,就可以识别一个块里面的所有交易。为此,先获得每笔交易的哈希,然后将它们关联起来,最后获得一个连接后的组合哈希。 201 | 202 | >比特币使用了一个更加复杂的技术:它将一个块里面包含的所有交易表示为一个  [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) ,然后在工作量证明系统中使用树的根哈希(root hash)。这个方法能够让我们快速检索一个块里面是否包含了某笔交易,即只需 root hash 而无需下载所有交易即可完成判断。 203 | 204 | 来检查一下到目前为止是否正确: 205 | 206 | ```bash 207 | $ blockchain_go createblockchain -address Ivan 208 | 00000093450837f8b52b78c25f8163bb6137caf43ff4d9a01d1b731fa8ddcc8a 209 | 210 | Done! 211 | ``` 212 | 213 | 很好!我们已经获得了第一笔挖矿奖励,但是,我们要如何查看余额呢? 214 | 215 | ## 未花费交易输出 216 | 217 | 我们需要找到所有的未花费交易输出(unspent transactions outputs, UTXO)。**未花费(unspent)** 指的是这个输出还没有被包含在任何交易的输入中,或者说没有被任何输入引用。在上面的图示中,未花费的输出是: 218 | 219 | 1. tx0, output 1; 220 | 2. tx1, output 0; 221 | 3. tx3, output 0; 222 | 4. tx4, output 0. 223 | 224 | 当然了,检查余额时,我们并不需要知道整个区块链上所有的 UTXO,只需要关注那些我们能够解锁的那些 UTXO(目前我们还没有实现密钥,所以我们将会使用用户定义的地址来代替)。首先,让我们定义在输入和输出上的锁定和解锁方法: 225 | 226 | ```go 227 | func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { 228 | return in.ScriptSig == unlockingData 229 | } 230 | 231 | func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { 232 | return out.ScriptPubKey == unlockingData 233 | } 234 | ``` 235 | 236 | 在这里,我们只是将 script 字段与 `unlockingData` 进行了比较。在后续文章我们基于私钥实现了地址以后,会对这部分进行改进。 237 | 238 | 下一步,找到包含未花费输出的交易,这一步其实相当困难: 239 | 240 | ```go 241 | func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { 242 | var unspentTXs []Transaction 243 | spentTXOs := make(map[string][]int) 244 | bci := bc.Iterator() 245 | 246 | for { 247 | block := bci.Next() 248 | 249 | for _, tx := range block.Transactions { 250 | txID := hex.EncodeToString(tx.ID) 251 | 252 | Outputs: 253 | for outIdx, out := range tx.Vout { 254 | // Was the output spent? 255 | if spentTXOs[txID] != nil { 256 | for _, spentOut := range spentTXOs[txID] { 257 | if spentOut == outIdx { 258 | continue Outputs 259 | } 260 | } 261 | } 262 | 263 | if out.CanBeUnlockedWith(address) { 264 | unspentTXs = append(unspentTXs, *tx) 265 | } 266 | } 267 | 268 | if tx.IsCoinbase() == false { 269 | for _, in := range tx.Vin { 270 | if in.CanUnlockOutputWith(address) { 271 | inTxID := hex.EncodeToString(in.Txid) 272 | spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) 273 | } 274 | } 275 | } 276 | } 277 | 278 | if len(block.PrevBlockHash) == 0 { 279 | break 280 | } 281 | } 282 | 283 | return unspentTXs 284 | } 285 | 286 | ``` 287 | 288 | 由于交易被存储在区块里,所以我们不得不检查区块链里的每一笔交易。从输出开始: 289 | 290 | ```go 291 | if out.CanBeUnlockedWith(address) { 292 | unspentTXs = append(unspentTXs, tx) 293 | } 294 | ``` 295 | 296 | 如果一个输出被一个地址锁定,并且这个地址恰好是我们要找的地址,那么这个输出就是我们想要的。不过在获取它之前,我们需要检查该输出是否已经被包含在一个交易的输入中,也就是检查它是否已经被花费了: 297 | 298 | ```go 299 | if spentTXOs[txID] != nil { 300 | for _, spentOut := range spentTXOs[txID] { 301 | if spentOut == outIdx { 302 | continue Outputs 303 | } 304 | } 305 | } 306 | ``` 307 | 308 | 我们跳过那些已经被包含在其他输入中的输出(这说明这个输出已经被花费,无法再用了)。检查完输出以后,我们将给定地址所有能够解锁输出的输入聚集起来(这并不适用于 coinbase 交易,因为它们不解锁输出): 309 | 310 | ```go 311 | if tx.IsCoinbase() == false { 312 | for _, in := range tx.Vin { 313 | if in.CanUnlockOutputWith(address) { 314 | inTxID := hex.EncodeToString(in.Txid) 315 | spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) 316 | } 317 | } 318 | } 319 | ``` 320 | 321 | 这个函数返回了一个交易列表,里面包含了未花费输出。为了计算余额,我们还需要一个函数将这些交易作为输入,然后仅返回一个输出: 322 | 323 | ```go 324 | func (bc *Blockchain) FindUTXO(address string) []TXOutput { 325 | var UTXOs []TXOutput 326 | unspentTransactions := bc.FindUnspentTransactions(address) 327 | 328 | for _, tx := range unspentTransactions { 329 | for _, out := range tx.Vout { 330 | if out.CanBeUnlockedWith(address) { 331 | UTXOs = append(UTXOs, out) 332 | } 333 | } 334 | } 335 | 336 | return UTXOs 337 | } 338 | ``` 339 | 340 | 就是这么多了!现在我们来实现 `getbalance` 命令: 341 | 342 | ```go 343 | func (cli *CLI) getBalance(address string) { 344 | bc := NewBlockchain(address) 345 | defer bc.db.Close() 346 | 347 | balance := 0 348 | UTXOs := bc.FindUTXO(address) 349 | 350 | for _, out := range UTXOs { 351 | balance += out.Value 352 | } 353 | 354 | fmt.Printf("Balance of '%s': %d\n", address, balance) 355 | } 356 | ``` 357 | 358 | 账户余额就是由账户地址锁定的所有未花费交易输出的总和。 359 | 360 | 在挖出创世块以后,来检查一下我们的余额: 361 | 362 | ```bash 363 | $ blockchain_go getbalance -address Ivan 364 | Balance of 'Ivan': 10 365 | ``` 366 | 367 | 这就是我们的第一笔钱! 368 | 369 | ## 发送币 370 | 371 | 现在,我们想要给其他人发送一些币。为此,我们需要创建一笔新的交易,将它放到一个块里,然后挖出这个块。之前我们只实现了 coinbase 交易(这是一种特殊的交易),现在我们需要一种通用的普通交易: 372 | 373 | ```go 374 | func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { 375 | var inputs []TXInput 376 | var outputs []TXOutput 377 | 378 | acc, validOutputs := bc.FindSpendableOutputs(from, amount) 379 | 380 | if acc < amount { 381 | log.Panic("ERROR: Not enough funds") 382 | } 383 | 384 | // Build a list of inputs 385 | for txid, outs := range validOutputs { 386 | txID, err := hex.DecodeString(txid) 387 | 388 | for _, out := range outs { 389 | input := TXInput{txID, out, from} 390 | inputs = append(inputs, input) 391 | } 392 | } 393 | 394 | // Build a list of outputs 395 | outputs = append(outputs, TXOutput{amount, to}) 396 | if acc > amount { 397 | outputs = append(outputs, TXOutput{acc - amount, from}) // a change 398 | } 399 | 400 | tx := Transaction{nil, inputs, outputs} 401 | tx.SetID() 402 | 403 | return &tx 404 | } 405 | ``` 406 | 407 | 在创建新的输出前,我们首先必须找到所有的未花费输出,并且确保它们有足够的价值(value),这就是 `FindSpendableOutputs` 方法要做的事情。随后,对于每个找到的输出,会创建一个引用该输出的输入。接下来,我们创建两个输出: 408 | 409 | 1. 一个由接收者地址锁定。这是给其他地址实际转移的币。 410 | 411 | 2. 一个由发送者地址锁定。这是一个找零。只有当未花费输出超过新交易所需时产生。记住:输出是**不可再分的**。 412 | 413 | `FindSpendableOutputs` 方法基于之前定义的 `FindUnspentTransactions` 方法: 414 | 415 | ```go 416 | func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { 417 | unspentOutputs := make(map[string][]int) 418 | unspentTXs := bc.FindUnspentTransactions(address) 419 | accumulated := 0 420 | 421 | Work: 422 | for _, tx := range unspentTXs { 423 | txID := hex.EncodeToString(tx.ID) 424 | 425 | for outIdx, out := range tx.Vout { 426 | if out.CanBeUnlockedWith(address) && accumulated < amount { 427 | accumulated += out.Value 428 | unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) 429 | 430 | if accumulated >= amount { 431 | break Work 432 | } 433 | } 434 | } 435 | } 436 | 437 | return accumulated, unspentOutputs 438 | } 439 | ``` 440 | 441 | 这个方法对所有的未花费交易进行迭代,并对它的值进行累加。当累加值大于或等于我们想要传送的值时,它就会停止并返回累加值,同时返回的还有通过交易 ID 进行分组的输出索引。我们只需取出足够支付的钱就够了。 442 | 443 | 现在,我们可以修改 `Blockchain.MineBlock` 方法: 444 | 445 | ```go 446 | func (bc *Blockchain) MineBlock(transactions []*Transaction) { 447 | ... 448 | newBlock := NewBlock(transactions, lastHash) 449 | ... 450 | } 451 | ``` 452 | 453 | 最后,让我们来实现 `send` 方法: 454 | 455 | ```go 456 | func (cli *CLI) send(from, to string, amount int) { 457 | bc := NewBlockchain(from) 458 | defer bc.db.Close() 459 | 460 | tx := NewUTXOTransaction(from, to, amount, bc) 461 | bc.MineBlock([]*Transaction{tx}) 462 | fmt.Println("Success!") 463 | } 464 | ``` 465 | 466 | 发送币意味着创建新的交易,并通过挖出新块的方式将交易打包到区块链中。不过,比特币并不是一连串立刻完成这些事情(虽然我们目前的实现是这么做的)。相反,它会将所有新的交易放到一个内存池中(mempool),然后当矿工准备挖出一个新块时,它就从内存池中取出所有交易,创建一个候选块。只有当包含这些交易的块被挖出来,并添加到区块链以后,里面的交易才开始确认。 467 | 468 | 让我们来检查一下发送币是否能工作: 469 | 470 | ```bash 471 | $ blockchain_go send -from Ivan -to Pedro -amount 6 472 | 00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37 473 | 474 | Success! 475 | 476 | $ blockchain_go getbalance -address Ivan 477 | Balance of 'Ivan': 4 478 | 479 | $ blockchain_go getbalance -address Pedro 480 | Balance of 'Pedro': 6 481 | ``` 482 | 483 | 很好!现在,让我们创建更多的交易,确保从多个输出中发送币也正常工作: 484 | 485 | ```bash 486 | $ blockchain_go send -from Pedro -to Helen -amount 2 487 | 00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf 488 | 489 | Success! 490 | 491 | $ blockchain_go send -from Ivan -to Helen -amount 2 492 | 000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa 493 | 494 | Success! 495 | ``` 496 | 497 | 现在,Helen 的币被锁定在了两个输出中:一个来自 Pedro,一个来自 Ivan。让我们把它们发送给其他人: 498 | 499 | ```bash 500 | $ blockchain_go send -from Helen -to Rachel -amount 3 501 | 000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0 502 | 503 | Success! 504 | 505 | $ blockchain_go getbalance -address Ivan 506 | Balance of 'Ivan': 2 507 | 508 | $ blockchain_go getbalance -address Pedro 509 | Balance of 'Pedro': 4 510 | 511 | $ blockchain_go getbalance -address Helen 512 | Balance of 'Helen': 1 513 | 514 | $ blockchain_go getbalance -address Rachel 515 | Balance of 'Rachel': 3 516 | ``` 517 | 518 | 看起来没问题!现在,来测试一些失败的情况: 519 | 520 | ```bash 521 | $ blockchain_go send -from Pedro -to Ivan -amount 5 522 | panic: ERROR: Not enough funds 523 | 524 | $ blockchain_go getbalance -address Pedro 525 | Balance of 'Pedro': 4 526 | 527 | $ blockchain_go getbalance -address Ivan 528 | Balance of 'Ivan': 2 529 | ``` 530 | 531 | ## 总结 532 | 533 | 虽然不容易,但是现在终于实现交易了!不过,我们依然缺少了一些像比特币那样的一些关键特性: 534 | 535 | 1. 地址(address)。我们还没有基于私钥(private key)的真实地址。 536 | 537 | 2. 奖励(reward)。现在挖矿是肯定无法盈利的! 538 | 539 | 3. UTXO 集。获取余额需要扫描整个区块链,而当区块非常多的时候,这么做就会花费很长时间。并且,如果我们想要验证后续交易,也需要花费很长时间。而 UTXO 集就是为了解决这些问题,加快交易相关的操作。 540 | 541 | 4. 内存池(mempool)。在交易被打包到块之前,这些交易被存储在内存池里面。在我们目前的实现中,一个块仅仅包含一笔交易,这是相当低效的。 542 | 543 | 参考: 544 | 545 | [1] [Full source codes](https://github.com/Jeiwan/blockchain_go/tree/part_4) 546 | 547 | [2] [Transaction](https://en.bitcoin.it/wiki/Transaction) 548 | 549 | [3] [Merkle tree](https://en.bitcoin.it/wiki/Protocol_documentation#Merkle_Trees) 550 | 551 | [4] [Coinbase](https://en.bitcoin.it/wiki/Coinbase) 552 | 553 | [5] [part_4](https://github.com/Jeiwan/blockchain_go/tree/part_4) 554 | 555 | [6] [Building Blockchain in Go. Part 4: Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/) 556 | -------------------------------------------------------------------------------- /content/part-6/transactions-2.md: -------------------------------------------------------------------------------- 1 | 交易(2) 2 | ======== 3 | 4 | ## 引言 5 | 6 | 在这个系列文章的一开始,我们就提到了,区块链是一个分布式数据库。不过在之前的文章中,我们选择性地跳过了“分布式”这个部分,而是将注意力都放到了“数据库”部分。到目前为止,我们几乎已经实现了一个区块链数据库的所有元素。今天,我们将会分析之前跳过的一些机制。而在下一篇文章中,我们将会开始讨论区块链的分布式特性。 7 | 8 | 之前的系列文章: 9 | 10 | 1. 基本原型 11 | 2. 工作量证明 12 | 3. 持久化和命令行接口 13 | 4. 交易(1) 14 | 5. 地址 15 | 16 | >本文的代码实现变化很大,请点击 [这里](https://github.com/Jeiwan/blockchain_go/compare/part_5...part_6#files_bucket) 查看所有的代码更改。 17 | 18 | ## 奖励 19 | 20 | 在上一篇文章中,我们略过的一个小细节是挖矿奖励。现在,我们已经可以来完善这个细节了。 21 | 22 | 挖矿奖励,实际上就是一笔 coinbase 交易。当一个挖矿节点开始挖出一个新块时,它会将交易从队列中取出,并在前面附加一笔 coinbase 交易。coinbase 交易只有一个输出,里面包含了矿工的公钥哈希。 23 | 24 | 实现奖励,非常简单,更新 `send` 即可: 25 | 26 | ```go 27 | func (cli *CLI) send(from, to string, amount int) { 28 | ... 29 | bc := NewBlockchain() 30 | UTXOSet := UTXOSet{bc} 31 | defer bc.db.Close() 32 | 33 | tx := NewUTXOTransaction(from, to, amount, &UTXOSet) 34 | cbTx := NewCoinbaseTX(from, "") 35 | txs := []*Transaction{cbTx, tx} 36 | 37 | newBlock := bc.MineBlock(txs) 38 | fmt.Println("Success!") 39 | } 40 | ``` 41 | 42 | 在我们的实现中,创建交易的人同时挖出了新块,所以会得到一笔奖励。 43 | 44 | ## UTXO 集 45 | 46 | 在 Part 3: 持久化和命令行接口 中,我们研究了 Bitcoin Core 是如何在一个数据库中存储块的,并且了解到区块被存储在 `blocks` 数据库,交易输出被存储在 `chainstate` 数据库。会回顾一下 `chainstate` 的机构: 47 | 48 | 1. `c` + 32 字节的交易哈希 -> 该笔交易的未花费交易输出记录 49 | 2. `B` + 32 字节的块哈希 -> 未花费交易输出的块哈希 50 | 51 | 在之前那篇文章中,虽然我们已经实现了交易,但是并没有使用 `chainstate` 来存储交易的输出。所以,接下来我们继续完成这部分。 52 | 53 | `chainstate` 不存储交易。它所存储的是 UTXO 集,也就是未花费交易输出的集合。除此以外,它还存储了“数据库表示的未花费交易输出的块哈希”,不过我们会暂时略过块哈希这一点,因为我们还没有用到块高度(但是我们会在接下来的文章中继续改进)。 54 | 55 | 那么,我们为什么需要 UTXO 集呢? 56 | 57 | 来思考一下我们早先实现的 `Blockchain.FindUnspentTransactions` 方法: 58 | 59 | ```go 60 | func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { 61 | ... 62 | bci := bc.Iterator() 63 | 64 | for { 65 | block := bci.Next() 66 | 67 | for _, tx := range block.Transactions { 68 | ... 69 | } 70 | 71 | if len(block.PrevBlockHash) == 0 { 72 | break 73 | } 74 | } 75 | ... 76 | } 77 | ``` 78 | 79 | 这个函数找到有未花费输出的交易。由于交易被保存在区块中,所以它会对区块链里面的每一个区块进行迭代,检查里面的每一笔交易。截止 2017 年 9 月 18 日,在比特币中已经有 485,860 个块,整个数据库所需磁盘空间超过 140 Gb。这意味着一个人如果想要验证交易,必须要运行一个全节点。此外,验证交易将会需要在许多块上进行迭代。 80 | 81 | 整个问题的解决方案是有一个仅有未花费输出的索引,这就是 UTXO 集要做的事情:这是一个从所有区块链交易中构建(对区块进行迭代,但是只须做一次)而来的缓存,然后用它来计算余额和验证新的交易。截止 2017 年 9 月,UTXO 集大概有 2.7 Gb。 82 | 83 | 好了,让我们来想一下实现 UTXO 集的话需要作出哪些改变。目前,找到交易用到了以下一些方法: 84 | 85 | 1. `Blockchain.FindUnspentTransactions` - 找到有未花费输出交易的主要函数。也是在这个函数里面会对所有区块进行迭代。 86 | 87 | 2. `Blockchain.FindSpendableOutputs` - 这个函数用于当一个新的交易创建的时候。如果找到有所需数量的输出。使用 `Blockchain.FindUnspentTransactions`. 88 | 89 | 3. `Blockchain.FindUTXO` - 找到一个公钥哈希的未花费输出,然后用来获取余额。使用 `Blockchain.FindUnspentTransactions`. 90 | 91 | 4. `Blockchain.FindTransation` - 根据 ID 在区块链中找到一笔交易。它会在所有块上进行迭代直到找到它。 92 | 93 | 可以看到,所有方法都对数据库中的所有块进行迭代。但是目前我们还没有改进所有方法,因为 UTXO 集没法存储所有交易,只会存储那些有未花费输出的交易。因此,它无法用于 `Blockchain.FindTransaction`。 94 | 95 | 所以,我们想要以下方法: 96 | 97 | 1. `Blockchain.FindUTXO` - 通过对区块进行迭代找到所有未花费输出。 98 | 99 | 2. `UTXOSet.Reindex` - 使用 `UTXO` 找到未花费输出,然后在数据库中进行存储。这里就是缓存的地方。 100 | 101 | 3. `UTXOSet.FindSpendableOutputs` - 类似 `Blockchain.FindSpendableOutputs`,但是使用 UTXO 集。 102 | 103 | 4. `UTXOSet.FindUTXO` - 类似 `Blockchain.FindUTXO`,但是使用 UTXO 集。 104 | 105 | 5. `Blockchain.FindTransaction` 跟之前一样。 106 | 107 | 因此,从现在开始,两个最常用的函数将会使用 cache!来开始写代码吧。 108 | 109 | ```go 110 | type UTXOSet struct { 111 | Blockchain *Blockchain 112 | } 113 | ``` 114 | 115 | 我们将会使用一个单一数据库,但是我们会将 UTXO 集从存储在不同的 bucket 中。因此,`UTXOSet` 跟 `Blockchain` 一起。 116 | 117 | ```go 118 | func (u UTXOSet) Reindex() { 119 | db := u.Blockchain.db 120 | bucketName := []byte(utxoBucket) 121 | 122 | err := db.Update(func(tx *bolt.Tx) error { 123 | err := tx.DeleteBucket(bucketName) 124 | _, err = tx.CreateBucket(bucketName) 125 | }) 126 | 127 | UTXO := u.Blockchain.FindUTXO() 128 | 129 | err = db.Update(func(tx *bolt.Tx) error { 130 | b := tx.Bucket(bucketName) 131 | 132 | for txID, outs := range UTXO { 133 | key, err := hex.DecodeString(txID) 134 | err = b.Put(key, outs.Serialize()) 135 | } 136 | }) 137 | } 138 | ``` 139 | 140 | 这个方法初始化了 UTXO 集。首先,如果 bucket 存在就先移除,然后从区块链中获取所有的未花费输出,最终将输出保存到 bucket 中。 141 | 142 | `Blockchain.FindUTXO` 几乎跟 `Blockchain.FindUnspentTransactions` 一模一样,但是现在它返回了一个 `TransactionID -> TransactionOutputs` 的 map。 143 | 144 | 现在,UTXO 集可以用于发送币: 145 | 146 | ```go 147 | func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { 148 | unspentOutputs := make(map[string][]int) 149 | accumulated := 0 150 | db := u.Blockchain.db 151 | 152 | err := db.View(func(tx *bolt.Tx) error { 153 | b := tx.Bucket([]byte(utxoBucket)) 154 | c := b.Cursor() 155 | 156 | for k, v := c.First(); k != nil; k, v = c.Next() { 157 | txID := hex.EncodeToString(k) 158 | outs := DeserializeOutputs(v) 159 | 160 | for outIdx, out := range outs.Outputs { 161 | if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { 162 | accumulated += out.Value 163 | unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) 164 | } 165 | } 166 | } 167 | }) 168 | 169 | return accumulated, unspentOutputs 170 | } 171 | ``` 172 | 173 | 或者检查余额: 174 | 175 | ```go 176 | func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { 177 | var UTXOs []TXOutput 178 | db := u.Blockchain.db 179 | 180 | err := db.View(func(tx *bolt.Tx) error { 181 | b := tx.Bucket([]byte(utxoBucket)) 182 | c := b.Cursor() 183 | 184 | for k, v := c.First(); k != nil; k, v = c.Next() { 185 | outs := DeserializeOutputs(v) 186 | 187 | for _, out := range outs.Outputs { 188 | if out.IsLockedWithKey(pubKeyHash) { 189 | UTXOs = append(UTXOs, out) 190 | } 191 | } 192 | } 193 | 194 | return nil 195 | }) 196 | 197 | return UTXOs 198 | } 199 | ``` 200 | 201 | 这是 `Blockchain` 方法的简单修改后的版本。这个 `Blockchain` 方法已经不再需要了。 202 | 203 | 有了 UTXO 集,也就意味着我们的数据(交易)现在已经被分开存储:实际交易被存储在区块链中,未花费输出被存储在 UTXO 集中。这样一来,我们就需要一个良好的同步机制,因为我们想要 UTXO 集时刻处于最新状态,并且存储最新交易的输出。但是我们不想每生成一个新块,就重新生成索引,因为这正是我们要极力避免的频繁区块链扫描。因此,我们需要一个机制来更新 UTXO 集: 204 | 205 | ```go 206 | func (u UTXOSet) Update(block *Block) { 207 | db := u.Blockchain.db 208 | 209 | err := db.Update(func(tx *bolt.Tx) error { 210 | b := tx.Bucket([]byte(utxoBucket)) 211 | 212 | for _, tx := range block.Transactions { 213 | if tx.IsCoinbase() == false { 214 | for _, vin := range tx.Vin { 215 | updatedOuts := TXOutputs{} 216 | outsBytes := b.Get(vin.Txid) 217 | outs := DeserializeOutputs(outsBytes) 218 | 219 | for outIdx, out := range outs.Outputs { 220 | if outIdx != vin.Vout { 221 | updatedOuts.Outputs = append(updatedOuts.Outputs, out) 222 | } 223 | } 224 | 225 | if len(updatedOuts.Outputs) == 0 { 226 | err := b.Delete(vin.Txid) 227 | } else { 228 | err := b.Put(vin.Txid, updatedOuts.Serialize()) 229 | } 230 | 231 | } 232 | } 233 | 234 | newOutputs := TXOutputs{} 235 | for _, out := range tx.Vout { 236 | newOutputs.Outputs = append(newOutputs.Outputs, out) 237 | } 238 | 239 | err := b.Put(tx.ID, newOutputs.Serialize()) 240 | } 241 | }) 242 | } 243 | ``` 244 | 245 | 虽然这个方法看起来有点复杂,但是它所要做的事情非常直观。当挖出一个新块时,应该更新 UTXO 集。更新意味着移除已花费输出,并从新挖出来的交易中加入未花费输出。如果一笔交易的输出被移除,并且不再包含任何输出,那么这笔交易也应该被移除。相当简单! 246 | 247 | 现在让我们在必要的时候使用 UTXO 集: 248 | 249 | ```go 250 | func (cli *CLI) createBlockchain(address string) { 251 | ... 252 | bc := CreateBlockchain(address) 253 | defer bc.db.Close() 254 | 255 | UTXOSet := UTXOSet{bc} 256 | UTXOSet.Reindex() 257 | ... 258 | } 259 | ``` 260 | 261 | 当一个新的区块链被创建以后,就会立刻进行重建索引。目前,这是 `Reindex` 唯一使用的地方,即使这里看起来有点“杀鸡用牛刀”,因为一条链开始的时候,只有一个块,里面只有一笔交易,`Update` 已经被使用了。不过我们在未来可能需要重建索引的机制。 262 | 263 | ```go 264 | func (cli *CLI) send(from, to string, amount int) { 265 | ... 266 | newBlock := bc.MineBlock(txs) 267 | UTXOSet.Update(newBlock) 268 | } 269 | ``` 270 | 271 | 当挖出一个新块时,UTXO 集就会进行更新。 272 | 273 | 让我们来检查一下如否如期工作: 274 | 275 | ```go 276 | $ blockchain_go createblockchain -address 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 277 | 00000086a725e18ed7e9e06f1051651a4fc46a315a9d298e59e57aeacbe0bf73 278 | 279 | Done! 280 | 281 | $ blockchain_go send -from 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 -to 12DkLzLQ4B3gnQt62EPRJGZ38n3zF4Hzt5 -amount 6 282 | 0000001f75cb3a5033aeecbf6a8d378e15b25d026fb0a665c7721a5bb0faa21b 283 | 284 | Success! 285 | 286 | $ blockchain_go send -from 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 -to 12ncZhA5mFTTnTmHq1aTPYBri4jAK8TacL -amount 4 287 | 000000cc51e665d53c78af5e65774a72fc7b864140a8224bf4e7709d8e0fa433 288 | 289 | Success! 290 | 291 | $ blockchain_go getbalance -address 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 292 | Balance of '1F4MbuqjcuJGymjcuYQMUVYB37AWKkSLif': 20 293 | 294 | $ blockchain_go getbalance -address 12DkLzLQ4B3gnQt62EPRJGZ38n3zF4Hzt5 295 | Balance of '1XWu6nitBWe6J6v6MXmd5rhdP7dZsExbx': 6 296 | 297 | $ blockchain_go getbalance -address 12ncZhA5mFTTnTmHq1aTPYBri4jAK8TacL 298 | Balance of '13UASQpCR8Nr41PojH8Bz4K6cmTCqweskL': 4 299 | ``` 300 | 301 | 很好!`1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1` 地址接收到了 3 笔奖励: 302 | 303 | 1. 一次是挖出创世块 304 | 2. 一次是挖出块 0000001f75cb3a5033aeecbf6a8d378e15b25d026fb0a665c7721a5bb0faa21b 305 | 3. 一个是挖出块 000000cc51e665d53c78af5e65774a72fc7b864140a8224bf4e7709d8e0fa433 306 | 307 | ## Merkle 树 308 | 309 | 在这篇文章中,我还想要再讨论一个优化机制。 310 | 311 | 上如上面所提到的,完整的比特币数据库(也就是区块链)需要超过 140 Gb 的磁盘空间。因为比特币的去中心化特性,网络中的每个节点必须是独立,自给自足的,也就是每个节点必须存储一个区块链的完整副本。随着越来越多的人使用比特币,这条规则变得越来越难以遵守:因为不太可能每个人都去运行一个全节点。并且,由于节点是网络中的完全参与者,它们负有相关责任:节点必须验证交易和区块。另外,要想与其他节点交互和下载新块,也有一定的网络流量需求。 312 | 313 | 在中本聪的 [比特币原始论文](https://bitcoin.org/bitcoin.pdf) 中,对这个问题也有一个解决方案:简易支付验证(Simplified Payment Verification, SPV)。SPV 是一个比特币轻节点,它不需要下载整个区块链,也**不需要验证区块和交易**。相反,它会在区块链查找交易(为了验证支付),并且需要连接到一个全节点来检索必要的数据。这个机制允许在仅运行一个全节点的情况下有多个轻钱包。 314 | 315 | 为了实现 SPV,需要有一个方式来检查是否一个区块包含了某笔交易,而无须下载整个区块。这就是 Merkle 树所要完成的事情。 316 | 317 | 比特币用 Merkle 树来获取交易哈希,哈希被保存在区块头中,并会用于工作量证明系统。到目前为止,我们只是将一个块里面的每笔交易哈希连接了起来,将在上面应用了 SHA-256 算法。虽然这是一个用于获取区块交易唯一表示的一个不错的途径,但是它没有利用到 Merkle 树。 318 | 319 | 来看一下 Merkle 树: 320 | 321 | ![Merkle tree](../images/127313-9c708d3c3d6a19c2.png) 322 | 323 | 每个块都会有一个 Merkle 树,它从叶子节点(树的底部)开始,一个叶子节点就是一个交易哈希(比特币使用双 SHA256 哈希)。叶子节点的数量必须是双数,但是并非每个块都包含了双数的交易。因为,如果一个块里面的交易数为单数,那么就将最后一个叶子节点(也就是 Merkle 树的最后一个交易,不是区块的最后一笔交易)复制一份凑成双数。 324 | 325 | 从下往上,两两成对,连接两个节点哈希,将组合哈希作为新的哈希。新的哈希就成为新的树节点。重复该过程,直到仅有一个节点,也就是树根。根哈希然后就会当做是整个块交易的唯一标示,将它保存到区块头,然后用于工作量证明。 326 | 327 | Merkle 树的好处就是一个节点可以在不下载整个块的情况下,验证是否包含某笔交易。并且这些只需要一个交易哈希,一个 Merkle 树根哈希和一个 Merkle 路径。 328 | 329 | 最后,来写代码: 330 | 331 | ```go 332 | type MerkleTree struct { 333 | RootNode *MerkleNode 334 | } 335 | 336 | type MerkleNode struct { 337 | Left *MerkleNode 338 | Right *MerkleNode 339 | Data []byte 340 | } 341 | ``` 342 | 343 | 先从结构体开始。每个 `MerkleNode` 包含数据和指向左右分支的指针。`MerkleTree` 实际上就是连接到下个节点的根节点,然后依次连接到更远的节点,等等。 344 | 345 | 让我们首先来创建一个新的节点: 346 | 347 | ```go 348 | func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode { 349 | mNode := MerkleNode{} 350 | 351 | if left == nil && right == nil { 352 | hash := sha256.Sum256(data) 353 | mNode.Data = hash[:] 354 | } else { 355 | prevHashes := append(left.Data, right.Data...) 356 | hash := sha256.Sum256(prevHashes) 357 | mNode.Data = hash[:] 358 | } 359 | 360 | mNode.Left = left 361 | mNode.Right = right 362 | 363 | return &mNode 364 | } 365 | ``` 366 | 367 | 每个节点包含一些数据。当节点在叶子节点,数据从外界传入(在这里,也就是一个序列化后的交易)。当一个节点被关联到其他节点,它会将其他节点的数据取过来,连接后再哈希。 368 | 369 | ```go 370 | func NewMerkleTree(data [][]byte) *MerkleTree { 371 | var nodes []MerkleNode 372 | 373 | if len(data)%2 != 0 { 374 | data = append(data, data[len(data)-1]) 375 | } 376 | 377 | for _, datum := range data { 378 | node := NewMerkleNode(nil, nil, datum) 379 | nodes = append(nodes, *node) 380 | } 381 | 382 | for i := 0; i < len(data)/2; i++ { 383 | var newLevel []MerkleNode 384 | 385 | for j := 0; j < len(nodes); j += 2 { 386 | node := NewMerkleNode(&nodes[j], &nodes[j+1], nil) 387 | newLevel = append(newLevel, *node) 388 | } 389 | 390 | nodes = newLevel 391 | } 392 | 393 | mTree := MerkleTree{&nodes[0]} 394 | 395 | return &mTree 396 | } 397 | ``` 398 | 399 | 当生成一棵新树时,要确保的第一件事就是叶子节点必须是双数。然后,**数据**(也就是一个序列化后交易的数组)被转换成树的叶子,从这些叶子再慢慢形成一棵树。 400 | 401 | [btcsuite/btcd](https://github.com/btcsuite/btcd/blob/50de9da05b50eb15658bb350f6ea24368a111ab7/blockchain/merkle.go#L71-L155) 是用数组实现的 merkle 树,因为这么做可以减少一半的内存使用。 402 | 403 | 现在,让我们来修改 `Block.HashTransactions`,它用于在工作量证明系统中获取交易哈希: 404 | 405 | ```go 406 | func (b *Block) HashTransactions() []byte { 407 | var transactions [][]byte 408 | 409 | for _, tx := range b.Transactions { 410 | transactions = append(transactions, tx.Serialize()) 411 | } 412 | mTree := NewMerkleTree(transactions) 413 | 414 | return mTree.RootNode.Data 415 | } 416 | ``` 417 | 418 | 首先,交易被序列化(使用 `encoding/gob`),然后使用序列后的交易构建一个 Mekle 树。树根将会作为块交易的唯一标识符。 419 | 420 | ## P2PKH 421 | 422 | 还有一件事情,我想要再谈一谈。 423 | 424 | 大家应该还记得,在比特币中有一个 *脚本(Script)*编程语言,它用于锁定交易输出;交易输入提供了解锁输出的数据。这个语言非常简单,用这个语言写的代码其实就是一系列数据和操作符而已。比如如下示例: 425 | 426 | ``` 427 | 5 2 OP_ADD 7 OP_EQUAL 428 | ``` 429 | 430 | 5, 2, 和 7 是数据,`OP_ADD` 和 `OP_EQUAL` 是操作符。*脚本*代码从左到右执行:将数据依次放入栈内,当遇到操作符时,就从栈内取出数据,并将操作符作用于数据,然后将结果作为栈顶元素。*脚本*的栈,实际上就是一个先进后出的内存存储:栈里的第一个元素最后一个取出,后面的每一个元素都会放到前一个元素之上。 431 | 432 | 让我们来对上面的脚本分部执行: 433 | 434 | 步骤 | 栈 | 脚本 | 说明 435 | :----: | :---- | :---- | :---- 436 | 1 | 空 | `5 2 OP_ADD 7 OP_EQUAL` | 一开始栈为空 437 | 2 | `5` | `2 OP_ADD 7 OP_EQUAL` | 从脚本里面取出 `5` 放入栈上 438 | 3 | `5 2` | `OP_ADD 7 OP_EQUAL` | 从脚本里面取出 `2` 放入栈上 439 | 4 | `7` | `7 OP_EQUAL` | 遇到操作符 `OP_ADD`, 从栈里取出两个操作数 `5` 和 `2`,相加后将结果放回栈上 440 | 5 | `7 7` | `OP_EQUAL` | 从脚本里面取出 `7` 放到栈上 441 | 6 | `true` | 空 | 遇到操作符 `OP_EQUAL`,从栈里取出两个操作数并比较,将比较的结果放回栈内,脚本执行完毕,为空 442 | 443 | `OP_ADD` 从栈内取两个元素,将这两个元素进行相加,然后将结果重新放回栈内。`OP_EQUAL` 从栈内取两个元素,然后对这两个元素进行比较:如果它们相等,就在栈上放一个 `true`,否则放一个 `false`。脚本执行的结果就是栈顶元素:在我们的案例中,如果是 `true`,那么表明脚本执行成功。 444 | 445 | 现在来看一下在比特币中,是如何用脚本执行支付的: 446 | 447 | ``` 448 | OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG 449 | ``` 450 | 451 | 这个脚本叫做 *Pay to Public Key Hash(P2PKH)*,这是比特币最常用的一个脚本。它所做的事情就是向一个公钥哈希支付,也就是说,用某一个公钥锁定一些币。这是**比特币支付的核心**:没有账户,没有资金转移;只有一个脚本检查提供的签名和公钥是否正确。 452 | 453 | 这个脚本实际存储为两个部分: 454 | 455 | 1. 第一个部分,` `,存储在输入的 `ScriptSig` 字段。 456 | 457 | 2. 第二部分,`OP_DUP OP_HASH160 OP_EQUALVERYFY OP_CHECKSIG` 存储在输出的 `ScriptPubKey` 里面。 458 | 459 | 因此,输出定了解锁的逻辑,输入提供解锁输出的“钥匙”。然我们来执行一下这个脚本: 460 | 461 | 步骤 | 栈 | 脚本 462 | :----: | :---- | :---- 463 | 1 | 空 | ` OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` 464 | 2 | `` | ` OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` 465 | 3 | ` ` | `OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` 466 | 4 | ` ` | `OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` 467 | 5 | ` ` | ` OP_EQUALVERIFY OP_CHECKSIG` 468 | 6 | ` ` | `OP_EQUALVERIFY OP_CHECKSIG` 469 | 7 | ` ` | `OP_CHECKSIG` 470 | 8 | `true` 或 `false` | 空 471 | 472 | `OP_DUP` 对栈顶元素进行复制。`OP_HASH160` 取栈顶元素,然后用 `RIPEMD160` 对它进行哈希,再将结果送回到栈上。`OP_EQUALVERIFY` 将栈顶的两个元素进行比较,如果它们不相等,终止脚本。`OP_CHECKSIG` 通过对交易进行哈希,并使用 `` 和 `pubKey` 来验证一笔交易的签名。最后的操作符有点复杂:它生成了一个修剪后的交易副本,对它进行哈希(因为它是一个被签名后的交易哈希),然后使用提供的 `` 和 `pubKey` 检查签名是否正确。 473 | 474 | 有了一个这样的脚本语言,实际上也可以让比特币成为一个智能合约平台:除了将一个单一的公钥转移资金,这个语言还使得一些其他的支付方案成为可能。 475 | 476 | ## 总结 477 | 478 | 这就是今天的全部内容了!我们已经实现了一个基于区块链的加密货币的几乎所有关键特性。我们已经有了区块链,地址,挖矿和交易。但是要想给这些所有的机制赋予生命,让比特币成为一个全球系统,还有一个不可或缺的环节:共识(consensus)。在下一篇文章中,我们将会开始实现区块链的“去中心化(decenteralized)”。敬请收听! 479 | 480 | 参考: 481 | 482 | 1. [Full source codes](https://github.com/Jeiwan/blockchain_go/tree/part_6) 483 | 2. [The UTXO Set](https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_2) 484 | 3. [Merkle Tree](https://en.bitcoin.it/wiki/Protocol_documentation#Merkle_Trees) 485 | 4. [Script](https://en.bitcoin.it/wiki/Script) 486 | 5. [“Ultraprune” Bitcoin Core commit](https://github.com/sipa/bitcoin/commit/450cbb0944cd20a06ce806e6679a1f4c83c50db2) 487 | 6. [UTXO set statistics](https://statoshi.info/dashboard/db/unspent-transaction-output-set) 488 | 7. [Smart contracts and Bitcoin](https://medium.com/@maraoz/smart-contracts-and-bitcoin-a5d61011d9b1) 489 | 8. [Why every Bitcoin user should understand “SPV security”](https://medium.com/@jonaldfyookball/why-every-bitcoin-user-should-understand-spv-security-520d1d45e0b9) 490 | 491 | 原文链接:[Building Blockchain in Go. Part 6: Transactions 2](https://jeiwan.cc/posts/building-blockchain-in-go-part-6/) 492 | -------------------------------------------------------------------------------- /content/part-7/network.md: -------------------------------------------------------------------------------- 1 | Part 7: 网络 2 | ============ 3 | 4 | ## 目录 5 | 6 | 7 | 8 | * [引言](#引言) 9 | * [区块链网络](#区块链网络) 10 | * [节点角色](#节点角色) 11 | * [网络简化](#网络简化) 12 | * [实现](#实现) 13 | * [场景](#场景) 14 | * [版本](#版本) 15 | * [getblocks](#getblocks) 16 | * [inv](#inv) 17 | * [getdata](#getdata) 18 | * [block 和 tx](#block-和-tx) 19 | * [结果](#结果) 20 | * [NODE 3000](#node-3000) 21 | * [NODE 3001](#node-3001) 22 | * [NODE 3000](#node-3000-1) 23 | * [NODE 3001](#node-3001-1) 24 | * [NODE 3002](#node-3002) 25 | * [NODE 3001](#node-3001-2) 26 | * [NODE 3002](#node-3002-1) 27 | * [NODE 3001](#node-3001-3) 28 | * [总结](#总结) 29 | 30 | 31 | 32 | ## 引言 33 | 34 | 到目前为止,我们所构建的原型已经具备了区块链所有的关键特性:匿名,安全,随机生成的地址;区块链数据存储;工作量证明系统;可靠地存储交易。尽管这些特性都不可或缺,但是仍有不足。能够使得这些特性真正发光发热,使得加密货币成为可能的,是**网络(network)**。如果实现的这样一个区块链仅仅运行在单一节点上,有什么用呢?如果只有一个用户,那么这些基于密码学的特性,又有什么用呢?正是由于网络,才使得整个机制能够运转和发光发热。 35 | 36 | 你可以将这些区块链特性认为是规则(rule),类似于人类在一起生活,繁衍生息建立的规则,一种社会安排。区块链网络就是一个程序社区,里面的每个程序都遵循同样的规则,正是由于遵循着同一个规则,才使得网络能够长存。类似的,当人们都有着同样的想法,就能够将拳头攥在一起构建一个更好的生活。如果有人遵循着不同的规则,那么他们就将生活在一个分裂的社区(州,公社,等等)中。同样的,如果有区块链节点遵循不同的规则,那么也会形成一个分裂的网络。 37 | 38 | **重点在于**:如果没有网络,或者大部分节点都不遵守同样的规则,那么规则就会形同虚设,毫无用处! 39 | 40 | >声明:不幸的是,我并没有足够的时间来实现一个真实的 P2P 网络原型。本文我会展示一个最常见的场景,这个场景涉及不同类型的节点。继续改进这个场景,将它实现为一个 P2P 网络,对你来说是一个很好的挑战和实践!除了本文的场景,我也无法保证在其他场景将会正常工作。抱歉! 41 | > 42 | >本文的代码实现变化很大,请点击 [这里](https://github.com/Jeiwan/blockchain_go/compare/part_6...part_7#files_bucket) 查看所有的代码更改。 43 | 44 | ## 区块链网络 45 | 46 | 区块链网络是去中心化的,这意味着没有服务器,客户端也不需要依赖服务器来获取或处理数据。在区块链网络中,有的是节点,每个节点是网络的一个完全(full-fledged)成员。节点就是一切:它既是一个客户端,也是一个服务器。这一点需要牢记于心,因为这与传统的网页应用非常不同。 47 | 48 | 区块链网络是一个 P2P(Peer-to-Peer,端到端)的网络,即节点直接连接到其他节点。它的拓扑是扁平的,因为在节点的世界中没有层级之分。下面是它的示意图: 49 | 50 | ![schematic representation](../images/127313-3c4174ad7cf6edf3.png) 51 | 52 | [Business vector created by Dooder - Freepik.com](http://www.freepik.com/dooder) 53 | 54 | 要实现这样一个网络节点更加困难,因为它们必须执行很多操作。每个节点必须与很多其他节点进行交互,它必须请求其他节点的状态,与自己的状态进行比较,当状态过时时进行更新。 55 | 56 | ## 节点角色 57 | 58 | 尽管节点具有完备成熟的属性,但是它们也可以在网络中扮演不同角色。比如: 59 | 60 | 1. 矿工 61 | 这样的节点运行于强大或专用的硬件(比如 ASIC)之上,它们唯一的目标是,尽可能快地挖出新块。矿工是区块链中唯一可能会用到工作量证明的角色,因为挖矿实际上意味着解决 PoW 难题。在权益证明 PoS 的区块链中,没有挖矿。 62 | 63 | 2. 全节点 64 | 这些节点验证矿工挖出来的块的有效性,并对交易进行确认。为此,他们必须拥有区块链的完整拷贝。同时,全节点执行路由操作,帮助其他节点发现彼此。对于网络来说,非常重要的一段就是要有足够多的全节点。因为正是这些节点执行了决策功能:他们决定了一个块或一笔交易的有效性。 65 | 66 | 3. SPV 67 | SPV 表示 Simplified Payment Verification,简单支付验证。这些节点并不存储整个区块链副本,但是仍然能够对交易进行验证(不过不是验证全部交易,而是一个交易子集,比如,发送到某个指定地址的交易)。一个 SPV 节点依赖一个全节点来获取数据,可能有多个 SPV 节点连接到一个全节点。SPV 使得钱包应用成为可能:一个人不需要下载整个区块链,但是仍能够验证他的交易。 68 | 69 | ## 网络简化 70 | 71 | 为了在目前的区块链原型中实现网络,我们不得不简化一些事情。因为我们没有那么多的计算机来模拟一个多节点的网络。当然,我们可以使用虚拟机或是 Docker 来解决这个问题,但是这会使一切都变得更复杂:你将不得不先解决可能出现的虚拟机或 Docker 问题,而我的目标是将全部精力都放在区块链实现上。所以,我们想要在一台机器上运行多个区块链节点,同时希望它们有不同的地址。为了实现这一点,我们将使用**端口号作为节点标识符**,而不是使用 IP 地址,比如将会有这样地址的节点:**127.0.0.1:3000**,**127.0.0.1:3001**,**127.0.0.1:3002** 等等。我们叫它端口节点(port node) ID,并使用环境变量 `NODE_ID` 对它们进行设置。故而,你可以打开多个终端窗口,设置不同的 `NODE_ID` 运行不同的节点。 72 | 73 | 这个方法也需要有不同的区块链和钱包文件。它们现在必须依赖于节点 ID 进行命名,比如 blockchain_3000.db, blockchain_30001.db and wallet_3000.db, wallet_30001.db 等等。 74 | 75 | ## 实现 76 | 77 | 所以,当你下载 Bitcoin Core 并首次运行时,到底发生了什么呢?它必须连接到某个节点下载最新状态的区块链。考虑到你的电脑并没有意识到所有或是部分的比特币节点,那么连接到的“某个节点”到底是什么? 78 | 79 | 在 Bitcoin Core 中硬编码一个地址,已经被证实是一个错误:因为节点可能会被攻击或关机,这会导致新的节点无法加入到网络中。在 Bitcoin Core 中,硬编码了 [DNS seeds](https://bitcoin.org/en/glossary/dns-seed)。虽然这些并不是节点,但是 DNS 服务器知道一些节点的地址。当你启动一个全新的 Bitcoin Core 时,它会连接到一个种子节点,获取全节点列表,随后从这些节点中下载区块链。 80 | 81 | 不过在我们目前的实现中,无法做到完全的去中心化,因为会出现中心化的特点。我们会有三个节点: 82 | 83 | 1. 一个中心节点。所有其他节点都会连接到这个节点,这个节点会在其他节点之间发送数据。 84 | 85 | 2. 一个矿工节点。这个节点会在内存池中存储新的交易,当有足够的交易时,它就会打包挖出一个新块。 86 | 87 | 3. 一个钱包节点。这个节点会被用作在钱包之间发送币。但是与 SPV 节点不同,它存储了区块链的一个完整副本。 88 | 89 | ## 场景 90 | 91 | 本文的目标是实现如下场景: 92 | 93 | 1. 中心节点创建一个区块链。 94 | 2. 一个其他(钱包)节点连接到中心节点并下载区块链。 95 | 3. 另一个(矿工)节点连接到中心节点并下载区块链。 96 | 4. 钱包节点创建一笔交易。 97 | 5. 矿工节点接收交易,并将交易保存到内存池中。 98 | 6. 当内存池中有足够的交易时,矿工开始挖一个新块。 99 | 7. 当挖出一个新块后,将其发送到中心节点。 100 | 8. 钱包节点与中心节点进行同步。 101 | 9. 钱包节点的用户检查他们的支付是否成功。 102 | 103 | 这就是比特币中的一般流程。尽管我们不会实现一个真实的 P2P 网络,但是我们会实现一个真实,也是比特币最常见最重要的用户场景。 104 | 105 | ## 版本 106 | 107 | 节点通过消息(message)进行交流。当一个新的节点开始运行时,它会从一个 DNS 种子获取几个节点,给它们发送 `version` 消息,在我们的实现看起来就像是这样: 108 | 109 | ```go 110 | type version struct { 111 | Version int 112 | BestHeight int 113 | AddrFrom string 114 | } 115 | ``` 116 | 117 | 由于我们仅有一个区块链版本,所以 `Version` 字段实际并不会存储什么重要信息。`BestHeight` 存储区块链中节点的高度。`AddFrom` 存储发送者的地址。 118 | 119 | 接收到 `version` 消息的节点应该做什么呢?它会响应自己的 `version` 消息。这是一种握手:如果没有事先互相问候,就不可能有其他交流。不过,这并不是出于礼貌:`version` 用于找到一个更长的区块链。当一个节点接收到 `version` 消息,它会检查本节点的区块链是否比 `BestHeight` 的值更大。如果不是,节点就会请求并下载缺失的块。 120 | 121 | 为了接收消息,我们需要一个服务器: 122 | 123 | ```go 124 | var nodeAddress string 125 | var knownNodes = []string{"localhost:3000"} 126 | 127 | func StartServer(nodeID, minerAddress string) { 128 | nodeAddress = fmt.Sprintf("localhost:%s", nodeID) 129 | miningAddress = minerAddress 130 | ln, err := net.Listen(protocol, nodeAddress) 131 | defer ln.Close() 132 | 133 | bc := NewBlockchain(nodeID) 134 | 135 | if nodeAddress != knownNodes[0] { 136 | sendVersion(knownNodes[0], bc) 137 | } 138 | 139 | for { 140 | conn, err := ln.Accept() 141 | go handleConnection(conn, bc) 142 | } 143 | } 144 | ``` 145 | 146 | 首先,我们对中心节点的地址进行硬编码:因为每个节点必须知道从何处开始初始化。`minerAddress` 参数指定了接收挖矿奖励的地址。代码片段: 147 | 148 | ```go 149 | if nodeAddress != knownNodes[0] { 150 | sendVersion(knownNodes[0], bc) 151 | } 152 | ``` 153 | 154 | 这意味着如果当前节点不是中心节点,它必须向中心节点发送 `version` 消息来查询是否自己的区块链已过时。 155 | 156 | ```go 157 | func sendVersion(addr string, bc *Blockchain) { 158 | bestHeight := bc.GetBestHeight() 159 | payload := gobEncode(version{nodeVersion, bestHeight, nodeAddress}) 160 | 161 | request := append(commandToBytes("version"), payload...) 162 | 163 | sendData(addr, request) 164 | } 165 | ``` 166 | 167 | 我们的消息,在底层就是字节序列。前 12 个字节指定了命令名(比如这里的 `version`),后面的字节会包含 **gob** 编码的消息结构,`commandToBytes` 看起来是这样: 168 | 169 | ```go 170 | func commandToBytes(command string) []byte { 171 | var bytes [commandLength]byte 172 | 173 | for i, c := range command { 174 | bytes[i] = byte(c) 175 | } 176 | 177 | return bytes[:] 178 | } 179 | ``` 180 | 181 | 它创建一个 12 字节的缓冲区,并用命令名进行填充,将剩下的字节置为空。下面一个相反的函数: 182 | 183 | ```go 184 | func bytesToCommand(bytes []byte) string { 185 | var command []byte 186 | 187 | for _, b := range bytes { 188 | if b != 0x0 { 189 | command = append(command, b) 190 | } 191 | } 192 | 193 | return fmt.Sprintf("%s", command) 194 | } 195 | ``` 196 | 197 | 当一个节点接收到一个命令,它会运行 `bytesToCommand` 来提取命令名,并选择正确的处理器处理命令主体: 198 | 199 | ```go 200 | func handleConnection(conn net.Conn, bc *Blockchain) { 201 | request, err := ioutil.ReadAll(conn) 202 | command := bytesToCommand(request[:commandLength]) 203 | fmt.Printf("Received %s command\n", command) 204 | 205 | switch command { 206 | ... 207 | case "version": 208 | handleVersion(request, bc) 209 | default: 210 | fmt.Println("Unknown command!") 211 | } 212 | 213 | conn.Close() 214 | } 215 | ``` 216 | 217 | 下面是 `version` 命令处理器: 218 | 219 | ```go 220 | func handleVersion(request []byte, bc *Blockchain) { 221 | var buff bytes.Buffer 222 | var payload verzion 223 | 224 | buff.Write(request[commandLength:]) 225 | dec := gob.NewDecoder(&buff) 226 | err := dec.Decode(&payload) 227 | 228 | myBestHeight := bc.GetBestHeight() 229 | foreignerBestHeight := payload.BestHeight 230 | 231 | if myBestHeight < foreignerBestHeight { 232 | sendGetBlocks(payload.AddrFrom) 233 | } else if myBestHeight > foreignerBestHeight { 234 | sendVersion(payload.AddrFrom, bc) 235 | } 236 | 237 | if !nodeIsKnown(payload.AddrFrom) { 238 | knownNodes = append(knownNodes, payload.AddrFrom) 239 | } 240 | } 241 | ``` 242 | 243 | 首先,我们需要对请求进行解码,提取有效信息。所有的处理器在这部分都类似,所以我们会下面的代码片段中略去这部分。 244 | 245 | 然后节点将从消息中提取的 `BestHeight` 与自身进行比较。如果自身节点的区块链更长,它会回复 `version` 消息;否则,它会发送 `getblocks` 消息。 246 | 247 | ## getblocks 248 | 249 | ```go 250 | type getblocks struct { 251 | AddrFrom string 252 | } 253 | ``` 254 | 255 | `getblocks` 意为 “给我看一下你有什么区块”(在比特币中,这会更加复杂)。注意,它并没有说“把你全部的区块给我”,而是请求了一个块哈希的列表。这是为了减轻网络负载,因为区块可以从不同的节点下载,并且我们不想从一个单一节点下载数十 GB 的数据。 256 | 257 | 处理命令十分简单: 258 | 259 | ```go 260 | func handleGetBlocks(request []byte, bc *Blockchain) { 261 | ... 262 | blocks := bc.GetBlockHashes() 263 | sendInv(payload.AddrFrom, "block", blocks) 264 | } 265 | ``` 266 | 267 | 在我们简化版的实现中,它会返回 **所有块哈希**。 268 | 269 | ## inv 270 | 271 | ```go 272 | type inv struct { 273 | AddrFrom string 274 | Type string 275 | Items [][]byte 276 | } 277 | ``` 278 | 279 | 比特币使用 `inv` 来向其他节点展示当前节点有什么块和交易。再次提醒,它没有包含完整的区块链和交易,仅仅是哈希而已。`Type` 字段表明了这是块还是交易。 280 | 281 | 处理 `inv` 稍显复杂: 282 | 283 | ```go 284 | func handleInv(request []byte, bc *Blockchain) { 285 | ... 286 | fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type) 287 | 288 | if payload.Type == "block" { 289 | blocksInTransit = payload.Items 290 | 291 | blockHash := payload.Items[0] 292 | sendGetData(payload.AddrFrom, "block", blockHash) 293 | 294 | newInTransit := [][]byte{} 295 | for _, b := range blocksInTransit { 296 | if bytes.Compare(b, blockHash) != 0 { 297 | newInTransit = append(newInTransit, b) 298 | } 299 | } 300 | blocksInTransit = newInTransit 301 | } 302 | 303 | if payload.Type == "tx" { 304 | txID := payload.Items[0] 305 | 306 | if mempool[hex.EncodeToString(txID)].ID == nil { 307 | sendGetData(payload.AddrFrom, "tx", txID) 308 | } 309 | } 310 | } 311 | ``` 312 | 313 | 如果收到块哈希,我们想要将它们保存在 `blocksInTransit` 变量来跟踪已下载的块。这能够让我们从不同的节点下载块。在将块置于传送状态时,我们给 `inv` 消息的发送者发送 `getdata` 命令并更新 `blocksInTransit`。在一个真实的 P2P 网络中,我们会想要从不同节点来传送块。 314 | 315 | 在我们的实现中,我们永远也不会发送有多重哈希的 `inv`。这就是为什么当 `payload.Type == "tx"` 时,只会拿到第一个哈希。然后我们检查是否在内存池中已经有了这个哈希,如果没有,发送 `getdata` 消息。 316 | 317 | ## getdata 318 | 319 | ```go 320 | type getdata struct { 321 | AddrFrom string 322 | Type string 323 | ID []byte 324 | } 325 | ``` 326 | 327 | `getdata` 用于某个块或交易的请求,它可以仅包含一个块或交易的 ID。 328 | 329 | ```go 330 | func handleGetData(request []byte, bc *Blockchain) { 331 | ... 332 | if payload.Type == "block" { 333 | block, err := bc.GetBlock([]byte(payload.ID)) 334 | 335 | sendBlock(payload.AddrFrom, &block) 336 | } 337 | 338 | if payload.Type == "tx" { 339 | txID := hex.EncodeToString(payload.ID) 340 | tx := mempool[txID] 341 | 342 | sendTx(payload.AddrFrom, &tx) 343 | } 344 | } 345 | ``` 346 | 347 | 这个处理器比较地直观:如果它们请求一个块,则返回块;如果它们请求一笔交易,则返回交易。注意,我们并不检查实际上是否已经有了这个块或交易。这是一个缺陷 :) 348 | 349 | ## block 和 tx 350 | 351 | ```go 352 | type block struct { 353 | AddrFrom string 354 | Block []byte 355 | } 356 | 357 | type tx struct { 358 | AddFrom string 359 | Transaction []byte 360 | } 361 | ``` 362 | 363 | 实际完成数据转移的正是这些消息。 364 | 365 | 处理 `block` 消息十分简单: 366 | 367 | ```go 368 | func handleBlock(request []byte, bc *Blockchain) { 369 | ... 370 | 371 | blockData := payload.Block 372 | block := DeserializeBlock(blockData) 373 | 374 | fmt.Println("Recevied a new block!") 375 | bc.AddBlock(block) 376 | 377 | fmt.Printf("Added block %x\n", block.Hash) 378 | 379 | if len(blocksInTransit) > 0 { 380 | blockHash := blocksInTransit[0] 381 | sendGetData(payload.AddrFrom, "block", blockHash) 382 | 383 | blocksInTransit = blocksInTransit[1:] 384 | } else { 385 | UTXOSet := UTXOSet{bc} 386 | UTXOSet.Reindex() 387 | } 388 | } 389 | ``` 390 | 391 | 当接收到一个新块时,我们把它放到区块链里面。如果还有更多的区块需要下载,我们继续从上一个下载的块的那个节点继续请求。当最后把所有块都下载完后,对 UTXO 集进行重新索引。 392 | 393 | >TODO:并非无条件信任,我们应该在将每个块加入到区块链之前对它们进行验证。 394 | > 395 | >TODO: 并非运行 UTXOSet.Reindex(), 而是应该使用 UTXOSet.Update(block),因为如果区块链很大,它将需要很多时间来对整个 UTXO 集重新索引。 396 | 397 | 处理 `tx` 消息是最困难的部分: 398 | 399 | ```go 400 | func handleTx(request []byte, bc *Blockchain) { 401 | ... 402 | txData := payload.Transaction 403 | tx := DeserializeTransaction(txData) 404 | mempool[hex.EncodeToString(tx.ID)] = tx 405 | 406 | if nodeAddress == knownNodes[0] { 407 | for _, node := range knownNodes { 408 | if node != nodeAddress && node != payload.AddFrom { 409 | sendInv(node, "tx", [][]byte{tx.ID}) 410 | } 411 | } 412 | } else { 413 | if len(mempool) >= 2 && len(miningAddress) > 0 { 414 | MineTransactions: 415 | var txs []*Transaction 416 | 417 | for id := range mempool { 418 | tx := mempool[id] 419 | if bc.VerifyTransaction(&tx) { 420 | txs = append(txs, &tx) 421 | } 422 | } 423 | 424 | if len(txs) == 0 { 425 | fmt.Println("All transactions are invalid! Waiting for new ones...") 426 | return 427 | } 428 | 429 | cbTx := NewCoinbaseTX(miningAddress, "") 430 | txs = append(txs, cbTx) 431 | 432 | newBlock := bc.MineBlock(txs) 433 | UTXOSet := UTXOSet{bc} 434 | UTXOSet.Reindex() 435 | 436 | fmt.Println("New block is mined!") 437 | 438 | for _, tx := range txs { 439 | txID := hex.EncodeToString(tx.ID) 440 | delete(mempool, txID) 441 | } 442 | 443 | for _, node := range knownNodes { 444 | if node != nodeAddress { 445 | sendInv(node, "block", [][]byte{newBlock.Hash}) 446 | } 447 | } 448 | 449 | if len(mempool) > 0 { 450 | goto MineTransactions 451 | } 452 | } 453 | } 454 | } 455 | ``` 456 | 457 | 首先要做的事情是将新交易放到内存池中(再次提醒,在将交易放到内存池之前,必要对其进行验证)。下个片段: 458 | 459 | ```go 460 | if nodeAddress == knownNodes[0] { 461 | for _, node := range knownNodes { 462 | if node != nodeAddress && node != payload.AddFrom { 463 | sendInv(node, "tx", [][]byte{tx.ID}) 464 | } 465 | } 466 | } 467 | ``` 468 | 469 | 检查当前节点是否是中心节点。在我们的实现中,中心节点并不会挖矿。它只会将新的交易推送给网络中的其他节点。 470 | 471 | 下一个很大的代码片段是矿工节点“专属”。让我们对它进行一下分解: 472 | 473 | ```go 474 | if len(mempool) >= 2 && len(miningAddress) > 0 { 475 | ``` 476 | 477 | `miningAddress` 只会在矿工节点上设置。如果当前节点(矿工)的内存池中有两笔或更多的交易,开始挖矿: 478 | 479 | ```go 480 | for id := range mempool { 481 | tx := mempool[id] 482 | if bc.VerifyTransaction(&tx) { 483 | txs = append(txs, &tx) 484 | } 485 | } 486 | 487 | if len(txs) == 0 { 488 | fmt.Println("All transactions are invalid! Waiting for new ones...") 489 | return 490 | } 491 | ``` 492 | 493 | 首先,内存池中所有交易都是通过验证的。无效的交易会被忽略,如果没有有效交易,则挖矿中断。 494 | 495 | ```go 496 | cbTx := NewCoinbaseTX(miningAddress, "") 497 | txs = append(txs, cbTx) 498 | 499 | newBlock := bc.MineBlock(txs) 500 | UTXOSet := UTXOSet{bc} 501 | UTXOSet.Reindex() 502 | 503 | fmt.Println("New block is mined!") 504 | ``` 505 | 506 | 验证后的交易被放到一个块里,同时还有附带奖励的 coinbase 交易。当块被挖出来以后,UTXO 集会被重新索引。 507 | 508 | >TODO: 提醒,应该使用 UTXOSet.Update 而不是 UTXOSet.Reindex. 509 | 510 | ```go 511 | for _, tx := range txs { 512 | txID := hex.EncodeToString(tx.ID) 513 | delete(mempool, txID) 514 | } 515 | 516 | for _, node := range knownNodes { 517 | if node != nodeAddress { 518 | sendInv(node, "block", [][]byte{newBlock.Hash}) 519 | } 520 | } 521 | 522 | if len(mempool) > 0 { 523 | goto MineTransactions 524 | } 525 | ``` 526 | 527 | 当一笔交易被挖出来以后,就会被从内存池中移除。当前节点所连接到的所有其他节点,接收带有新块哈希的 `inv` 消息。在处理完消息后,它们可以对块进行请求。 528 | 529 | ## 结果 530 | 531 | 让我们来回顾一下上面定义的场景。 532 | 533 | 首先,在第一个终端窗口中将 `NODE_ID` 设置为 3000(`export NODE_ID=3000`)。为了让你知道什么节点执行什么操作,我会使用像 **NODE 3000** 或 **NODE 3001** 进行标识。 534 | 535 | ### NODE 3000 536 | 537 | 创建一个钱包和一个新的区块链: 538 | 539 | ```bash 540 | $ blockchain_go createblockchain -address CENTREAL_NODE 541 | ``` 542 | (为了简洁起见,我会使用假地址。) 543 | 544 | 然后,会生成一个仅包含创世块的区块链。我们需要保存块,并在其他节点使用。创世块承担了一条链标识符的角色(在 Bitcoin Core 中,创世块是硬编码的) 545 | 546 | ```bash 547 | $ cp blockchain_3000.db blockchain_genesis.db 548 | ``` 549 | 550 | ### NODE 3001 551 | 552 | 接下来,打开一个新的终端窗口,将 node ID 设置为 3001。这会作为一个钱包节点。通过 `blockchain_go createwallet` 生成一些地址,我们把这些地址叫做 WALLET_1, WALLET_2, WALLET_3. 553 | 554 | ### NODE 3000 555 | 556 | 向钱包地址发送一些币: 557 | 558 | ```bash 559 | $ blockchain_go send -from CENTREAL_NODE -to WALLET_1 -amount 10 -mine 560 | $ blockchain_go send -from CENTREAL_NODE -to WALLET_2 -amount 10 -mine 561 | ``` 562 | 563 | `-mine` 标志指的是块会立刻被同一节点挖出来。我们必须要有这个标志,因为初始状态时,网络中没有矿工节点。 564 | 565 | 启动节点: 566 | 567 | ```bash 568 | $ blockchain_go startnode 569 | ``` 570 | 571 | 这个节点会持续运行,直到本文定义的场景结束。 572 | 573 | ### NODE 3001 574 | 575 | 启动上面保存创世块节点的区块链: 576 | 577 | ```bash 578 | $ cp blockchain_genesis.db blockchain_3001.db 579 | ``` 580 | 581 | 运行节点: 582 | 583 | ```go 584 | $ blockchain_go startnode 585 | ``` 586 | 587 | 它会从中心节点下载所有区块。为了检查一切正常,暂停节点运行并检查余额: 588 | 589 | ```bash 590 | $ blockchain_go getbalance -address WALLET_1 591 | Balance of 'WALLET_1': 10 592 | 593 | $ blockchain_go getbalance -address WALLET_2 594 | Balance of 'WALLET_2': 10 595 | ``` 596 | 597 | 你还可以检查 `CENTRAL_NODE` 地址的余额,因为 node 3001 现在有它自己的区块链: 598 | 599 | ```bash 600 | $ blockchain_go getbalance -address CENTRAL_NODE 601 | Balance of 'CENTRAL_NODE': 10 602 | ``` 603 | 604 | ### NODE 3002 605 | 606 | 打开一个新的终端窗口,将它的 ID 设置为 3002,然后生成一个钱包。这会是一个矿工节点。初始化区块链: 607 | 608 | ```bash 609 | $ cp blockchain_genesis.db blockchain_3002.db 610 | ``` 611 | 612 | 启动节点: 613 | 614 | ```bash 615 | $ blockchain_go startnode -miner MINER_WALLET 616 | ``` 617 | 618 | ### NODE 3001 619 | 620 | 发送一些币: 621 | 622 | ```bash 623 | $ blockchain_go send -from WALLET_1 -to WALLET_3 -amount 1 624 | $ blockchain_go send -from WALLET_2 -to WALLET_4 -amount 1 625 | ``` 626 | 627 | ### NODE 3002 628 | 629 | 迅速切换到矿工节点,你会看到挖出了一个新块!同时,检查中心节点的输出。 630 | 631 | ### NODE 3001 632 | 633 | 切换到钱包节点并启动: 634 | 635 | ```bash 636 | $ blockchain_go startnode 637 | ``` 638 | 639 | 它会下载最近挖出来的块! 640 | 641 | 暂停节点并检查余额: 642 | 643 | ```bash 644 | $ blockchain_go getbalance -address WALLET_1 645 | Balance of 'WALLET_1': 9 646 | 647 | $ blockchain_go getbalance -address WALLET_2 648 | Balance of 'WALLET_2': 9 649 | 650 | $ blockchain_go getbalance -address WALLET_3 651 | Balance of 'WALLET_3': 1 652 | 653 | $ blockchain_go getbalance -address WALLET_4 654 | Balance of 'WALLET_4': 1 655 | 656 | $ blockchain_go getbalance -address MINER_WALLET 657 | Balance of 'MINER_WALLET': 10 658 | ``` 659 | 660 | 就是这么多了! 661 | 662 | ## 总结 663 | 664 | 这是本系列的最后一篇文章了。我本可以就实现一个真实的 P2P 网络原型继续展开,但是我真的没有这么多时间。我希望本文已经回答了关于比特币技术的一些问题,也给读者提出了一些问题,这些问题你可以自行寻找答案。在比特币技术中还有隐藏着很多有趣的事情!好运! 665 | 666 | 后记:你可以从实现 `addr` 消息来开始改进网络,正如比特币网络协议中所描述的(链接可以下方找到)那样。这是一个非常重要的消息,因为它允许节点来互相发现彼此。我已经开始实现了,不过还没有完成! 667 | 668 | 链接: 669 | 670 | 1. [Source codes](https://github.com/Jeiwan/blockchain_go/tree/part_7) 671 | 2. [Bitcoin protocol documentation](https://en.bitcoin.it/wiki/Protocol_documentation) 672 | 3. [Bitcoin network](https://en.bitcoin.it/wiki/Network) 673 | 674 | 原文:[Building Blockchain in Go. Part 7: Network](https://jeiwan.cc/posts/building-blockchain-in-go-part-7/) 675 | -------------------------------------------------------------------------------- /content/polkadot/lightpaper.md: -------------------------------------------------------------------------------- 1 | # Lightpaper 2 | 3 | > https://polkadot.network/Polkadot-lightpaper.pdf 4 | 5 | 6 | 1. Scalability 7 | 8 | 2. Governance 9 | 10 | 3. Isolability 11 | 12 | 4. Developability 13 | 14 | 5. Applicability 15 | -------------------------------------------------------------------------------- /content/solidity.md: -------------------------------------------------------------------------------- 1 | # Solidity 2 | 3 | - [Anonymous function in Solidity example code](https://ethereum.stackexchange.com/questions/24439/anonymous-function-in-solidity-example-code) 4 | 5 | - [What is an Event?](https://ethereum.stackexchange.com/questions/11228/what-is-an-event) 6 | - [eth_newfilter](https://wiki.parity.io/JSONRPC-eth-module.html#eth_newfilter) 7 | - [Where do contract event logs get stored in the Ethereum architecture?](https://ethereum.stackexchange.com/questions/1302/where-do-contract-event-logs-get-stored-in-the-ethereum-architecture) 8 | - [How to implement events in solidity](https://ethereum.stackexchange.com/questions/2654/how-to-implement-events-in-solidity) 9 | -------------------------------------------------------------------------------- /docs/Consensus.md: -------------------------------------------------------------------------------- 1 | - [A Proof of Stake overview](https://medium.com/@poolofstake/a-proof-of-stake-overview-445c52558d03) 2 | - [Basecoin (aka the Basis Protocol): the worst idea in cryptocurrency, reborn](https://prestonbyrne.com/2017/10/13/basecoin-bitshares-2-electric-boogaloo/) 3 | - [Analysis of Casper PoW Reward Reduction](https://gist.github.com/djrtwo/bc864c0d0a275170183803814b207b9a) 4 | - [Blockchain Proof-of-Work Is a Decentralized Clock](https://grisha.org/blog/2018/01/23/explaining-proof-of-work/) 5 | - [reddit: Proof of Stake is solved](https://www.reddit.com/r/ethereum/comments/8f63la/proof_of_stake_is_solved/) 6 | - [Ethereum Infighting Spurs Blockchain Split Concerns](https://www.coindesk.com/even-ethereums-top-developers-think-blockchain-split-might-inevitable/) 7 | - [Quick comparison of blockchain scaling solutions](https://twitter.com/liamihorne/status/989302230065991680) 8 | - [Stablecoins: designing a price-stable cryptocurrency](https://hackernoon.com/stablecoins-designing-a-price-stable-cryptocurrency-6bf24e2689e5) 9 | - [The Mystery Behind Block Time](https://medium.facilelogin.com/the-mystery-behind-block-time-63351e35603a) 10 | - [Money in the modern economy:an introduction](https://www.bankofengland.co.uk/-/media/boe/files/quarterly-bulletin/2014/money-in-the-modern-economy-an-introduction.pdf?la=en&hash=E43CDFDBB5A23D672F4D09B13DF135E6715EEDAC) 11 | - [Blockchain Oracles Will Make Smart Contracts Fly](https://hackernoon.com/oracles-help-smart-contracts-resolve-subjective-events-d81639d8291c) 12 | - [The State of Cryptocurrency Mining](https://blog.sia.tech/the-state-of-cryptocurrency-mining-538004a37f9b?nsukey=gy%20vHoDVv9nTcmMI3f%20cCCwu5c73L3NB%2Fog16BWS4CLZu4ZK6Di3UUruE22xaEbcretfSpdTZxc32cY%20XlElGk2DjsYeFPVIuY6qSUr4I4XbEWz28m3IYEO5UWp%2FL8pM60dMbURLZqAgCEiz7JGsCEiDWKSwU1ARvx4d9HavJVbUiujiSzoO1ZDGNVF%20ls%20%20u73TtpVBPxq08kPRWrPFQg%3D%3D) 13 | - [The Ethereum-blockchain size has exceeded 1TB, and yes, it’s an issue](https://hackernoon.com/the-ethereum-blockchain-size-has-exceeded-1tb-and-yes-its-an-issue-2b650b5f4f62) 14 | - [Counterfactual: Generalized State Channels on Ethereum](https://medium.com/statechannels/counterfactual-generalized-state-channels-on-ethereum-d38a36d25fc6) 15 | - [EOS DPOS BFT](https://github.com/EOSIO/eos/issues/2718) 16 | - [Consensus, Casper and Cryptoeconomics in 15 minutes or less](https://medium.com/@jpa_of_snc/consensus-casper-and-cryptoeconomics-in-15-minutes-or-less-c7ca2427bf88) 17 | - [Tendermint: Byzantine Fault Tolerance in the Age of Blockchains](https://allquantor.at/blockchainbib/pdf/buchman2016tendermint.pdf) 18 | - [区块链时代的拜占庭容错:Tendermint(一)](https://mp.weixin.qq.com/s/4K46OB5KDJnCtLyOwyvhJw) 19 | - [区块链时代的拜占庭容错:Tendermint(二)](https://mp.weixin.qq.com/s/n_lRygSI_GTrnSct_ZNmxw) 20 | - [Vitalik explains the history and state of Ethereum's Casper research](https://twitter.com/VitalikButerin/status/1029906887376961536) 21 | - [完整推文的 markdown 版本](https://hackmd.io/s/BJZDR1mIX#) 22 | - [V 神连发 75 条 Twitter,告诉你什么是以太坊 Casper](https://www.chainnews.com/articles/744537451587.htm) 23 | - [连发75条推文!今天激动的V神到底想表达什么?(附翻译)](https://zhuanlan.zhihu.com/p/42261481) 24 | -------------------------------------------------------------------------------- /docs/Cryptography.md: -------------------------------------------------------------------------------- 1 | - [Faster Bulletproofs with Ristretto & AVX2](https://blog.chain.com/faster-bulletproofs-with-ristretto-avx2-29450b4490cd) 2 | - [Hash-based Signatures: An illustrated Primer](https://blog.cryptographyengineering.com/2018/04/07/hash-based-signatures-an-illustrated-primer/) 3 | -------------------------------------------------------------------------------- /docs/General.md: -------------------------------------------------------------------------------- 1 | - [Why Blockchain is Hard](https://medium.com/@jimmysong/why-blockchain-is-hard-60416ea4c5c) 2 | - [Blockchain vs. Distributed Ledger Technologies](https://media.consensys.net/blockchain-vs-distributed-ledger-technologies-1e0289a87b16) 3 | - [Understanding Blockchain Fundamentals, Part 3: Delegated Proof of Stake](https://medium.com/loom-network/understanding-blockchain-fundamentals-part-3-delegated-proof-of-stake-b385a6b92ef) 4 | - [The Psychology of Money](http://www.collaborativefund.com/blog/the-psychology-of-money/) 5 | - [Why Everyone Missed the Most Mind-Blowing Feature of Cryptocurrency](https://hackernoon.com/why-everyone-missed-the-most-mind-blowing-feature-of-cryptocurrency-860c3f25f1fb) 6 | - [Blockchain is not only crappy technology but a bad vision for the future](https://medium.com/@kaistinchcombe/decentralized-and-trustless-crypto-paradise-is-actually-a-medieval-hellhole-c1ca122efdec) 7 | - [Why Tezos is the most advanced Blockchain to date](https://medium.com/@williammckenzie1997/why-tezos-is-the-most-advanced-blockchain-to-date-e1ef29a3880a) 8 | 9 | - [A glimpse into the dark underbelly of cryptocurrency markets](https://medium.com/@nic__carter/a-glimpse-into-the-dark-underbelly-of-cryptocurrency-markets-d1690b761eaf) 10 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

Opinioned Blockchain Reading List

2 |

A collection of blockchain resources.

3 | 4 | ## [General](General) 5 | 6 | ## [Consensus](Consensus) 7 | 8 | ## [Cryptography](Cryptography) 9 | 10 | ## [Web3](Web3) 11 | 12 | ## Interoperability 13 | 14 | - [Blockchain Interoperability: Cosmos vs. Polkadot](https://medium.com/@davekaj/blockchain-interoperability-cosmos-vs-polkadot-48097d54d2e2) 15 | - [区块链的互操作性:Cosmos vs Polkadot](https://mp.weixin.qq.com/s/EiRzV-y-lakROiQQpAuVgg) 16 | 17 | ## Bitcoin 18 | 19 | - [Leaf-Node weakness in Bitcoin Merkle Tree Design](https://bitslog.wordpress.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design/) 20 | - [SDLerner](https://twitter.com/SDLerner/status/1005541449507721218) 21 | - [Lessons on Bubbles From Bitcoin](https://www.bloomberg.com/view/articles/2018-04-10/bitcoin-was-prone-to-bubbles-until-bears-could-bet-against-it) 22 | - [Explanation of what an OP_RETURN transaction looks like](https://bitcoin.stackexchange.com/questions/29554/explanation-of-what-an-op-return-transaction-looks-like) 23 | - [https://news.ycombinator.com/item?id=16862077](https://news.ycombinator.com/item?id=16862077) 24 | - https://bitcoin.stackexchange.com/questions/12954/why-the-timestamp-component-of-the-block-header 25 | - https://www.reddit.com/r/Bitcoin/comments/2r04xi/genesis_block_and_following_blocks_timestamps_are/ 26 | - https://bitcoin.stackexchange.com/questions/786/is-the-timestamp-in-the-genesis-block-relevant 27 | - https://github.com/ZtesoftCS/go-ethereum-code-analysis 28 | 29 | ## Ethereum 30 | 31 | - [Getting Deep Into EVM: How Ethereum Works Backstage](https://hackernoon.com/getting-deep-into-evm-how-ethereum-works-backstage-ac7efa1f0015) 32 | 33 | ## Governance 34 | 35 | - [Blockchains should not be democracies](https://haseebq.com/blockchains-should-not-be-democracies/) 36 | 37 | ## Scaling 38 | 39 | - [sharding](https://www.reddit.com/r/ethereum/comments/8g1q55/vitalik_teases_sharding_release_on_twitter/dy85pq0/) 40 | - [How to Scale Ethereum: Sharding Explained](https://medium.com/prysmatic-labs/how-to-scale-ethereum-sharding-explained-ba2e283b7fce) 41 | 42 | ## Fundamental Concepts 43 | 44 | ### Digital singatures 45 | 46 | - http://learnmeabitcoin.com/guide/digital_signatures 47 | 48 | ### Fork 49 | 50 | - https://en.bitcoin.it/wiki/Hardfork 51 | - https://cryptocurrencyfacts.com/block-number-matters-with-crypto-forks/ 52 | 53 | ### Ricardian Contracts 54 | 55 | - [programmer walk thru of Ricardian Contract in EOS](https://vimeo.com/264667031) 56 | - [Ricardian contracts — legally binding agreements on the blockchain](https://medium.com/legalthingsone/ricardian-contracts-legally-binding-agreements-on-the-blockchain-4c103f120707) 57 | - [Smart Contracts — 3 limitations of a self-enforcing agreement](https://medium.com/legalthingsone/smart-contracts-3-limitations-of-a-self-enforcing-agreement-257cfbabeff5) 58 | - [Live Contracts — What’s all the fuss about?](https://medium.com/legalthingsone/live-contracts-whats-all-the-fuss-about-c4e4c0288157) 59 | 60 | ### Replay Attacks 61 | 62 | - [Replay Attacks Explained](https://bitcointechtalk.com/replay-attacks-explained-e3d6d2ea0ab2) 63 | 64 | ### Self Mining 65 | 66 | - [ON PROFITABILITY OF SELFISH MINING](https://webusers.imj-prg.fr/~ricardo.perez-marco/publications/articles/OnSelfishMining20.pdf) 67 | 68 | - [multisignature vs threshold signature](https://cointelegraph.com/news/threshold-signatures-are-a-significant-milestone-in-bitcoin-security) 69 | 70 | ### Other recommandations 71 | 72 | - https://github.com/bftfio/bftfio.github.io 73 | -------------------------------------------------------------------------------- /docs/Web3.md: -------------------------------------------------------------------------------- 1 | ## Web3 2 | 3 | - [Ultimate Guide to Convert a Web App To a Decentralized App Dapp](https://medium.com/@merunasgrincalaitis/ultimate-guide-to-convert-a-web-app-to-a-decentralized-app-dapp-f6112a079509) 4 | - [Web3 Design Principles](https://medium.com/@lyricalpolymath/web3-design-principles-f21db2f240c1) 5 | - [Making Sense of Web 3](https://medium.com/l4-media/making-sense-of-web-3-c1a9e74dcae) 6 | - [理解 Web 3](https://ethfans.org/posts/making-sense-of-web-3) 7 | - [Why the Web 3.0 Matters and you should know about it](https://medium.com/@matteozago/why-the-web-3-0-matters-and-you-should-know-about-it-a5851d63c949) 8 | - [2017 Was the Year of Blockchain Awareness. 2018 Is the Year of Adoption](https://medium.com/@matteozago/2017-was-the-year-of-blockchain-education-2018-is-the-year-of-adoption-bb862e0faae5) 9 | 10 | - [Why We Need Web 3.0](https://medium.com/s/the-crypto-collection/why-we-need-web-3-0-5da4f2bf95ab) 11 | - [Gavin Wood: 我与以太坊的二三事](https://mp.weixin.qq.com/s/pE1kjQ5UpKb5k6Qo3eirnA) 12 | -------------------------------------------------------------------------------- /styles/website.css: -------------------------------------------------------------------------------- 1 | h1, h2 { 2 | border-bottom: 1px solid #EFEAEA; 3 | } 4 | --------------------------------------------------------------------------------