以太坊图解

以太坊(Ethereum)是一个分布式的计算机,由大量分布式节点组成,其中的每一个节点,都可以执行字节码(也就是所谓智能合约),然后把结果存在区块链上。整个网络是分布式的,应用可以保存状态信息,应用有了状态信息,就可以根据状态提供丰富多彩的服务。以太坊没有中心化的节点,第三方不能干预整个网络的运行,可以看作是永不停机的世界计算机。 1. 以太坊-世界计算机 上图描述了以太坊的功能。 它把一台虚拟计算机放到了区块链上。 图中彩色方块是用哈希值串联起来的区块,每一个区块中有交易列表,交易在计算机中执行,并且导致计算机状态的变化。 以太坊中把这个虚拟计算机的状态叫做“世界状态”, 世界状态可以看作“世界计算机”的存储区域。 世界状态通过执行以太坊区块中的交易来不断变化。每一个区块添加到链的尾部,都会导致世界的状态发生变化。世界状态由各个节点独立在数据库中维护。 从这个视角看,这种状态管理的方式,类似git,每一个区块都是一个新的版本。每一个区块都可以得到一个当前所有账户的状态。 最后一个区块,就是当前系统中所有账户的当前状态。 世界计算机在以太坊的每一个节点上运行,每一个节点上的世界状态都一样。 交易触发的代码会在所有节点的虚拟机上运行一遍。 2. 账户 世界状态由所有账户的状态构成。 有两种账户,分别是外部账户和合约账户。账户的属性包括,余额(balance)、交易次数(nonce),code(代码)、存储(stroge)。 code和 storage只有合约账户有。外部账户用私钥控制,合约账户由合约代码控制。 每一个内部账户都有自己独立的storage区域。以太坊中的智能合约代码,一旦部署,是无法升级修改的。codeHash确保代码不会被修改。 上图中只画了两个账户,实际的世界状态中有很多账户。 3. 交易 以太坊的交易由网络用户发起,交易有两种,一种是普通消息调用,另外一种是创建内部账户(会生成一个新的地址,初始化智能合约)。 上图展示了交易执行后, 世界状态中多了一个内部账户。 4. EVM 虚拟机 作为世界计算机, 不仅仅有存储, 还有执行智能合约代码的虚拟机。称之为EVM。合约代码在EVM中执行,可以导致存储(世界状态)的变化。 智能合约开发使用高级语言,在部署到以太坊上时候,会编译成EVM可以识别的二进制串。合约一旦部署,就需要在所有维护世界状态的节点执行。注意不是在一个节点执行,而是一旦交易列表中有合约相关操作,则操作对应的代码要在所有节点执行,以太坊节点通过执行合约相关代码来更新世界状态。 对世界计算机的使用是收费的,每次执行需要预付费(术语叫gas),执行过程中不断扣减费用。扣完了预付费后,指令将不能再执行,智能合约的调用失败。 这种机制也增进了系统的可靠性。 如果智能合约中出了bug,写了一个死循环,至少还能欠费停下来。 5. 访问以太坊 外部用户通过以太坊节点,使用web3 API访问以太坊。 6. 数据结构 上图展示了以太坊的主要数据结构。最左侧是一个以太坊的区块,包括区块头和区块体。在区块头里面除了parentHash作为区块指针外,还有几个哈希值: stateRoot:其值是这个区块内所有交易执行后世界状态的哈希值。 receiptsRoot:其值是这个区块所有交易的执行结果的哈希值。 transactionRoot:其值是这个区块所有交易的哈希值。 ommersHash(UncleHash): 是对区块体中的叔块列表的哈希。 区块体中存放交易列表和叔块列表。 右侧的几棵merkle tree(世界状态,交易状态, 交易等),不会在区块中存储,而是会在节点的数据库中保存。 用区块中的哈希值来保证各个节点的merkle tree状态都一致。 7. 参考资料 getting-deep-into-ethereum-how-data-is-stored-in-ethereum 以太坊黄皮书 以太坊白皮书 DanFinlay-intro_to_ethereum

2022年4月4日 · 1 分钟 · 费小慢

比特币和区块链

《比特币白皮书》是一篇很短的论文, 只有12小段。 Satoshi Nakamoto发表于2008年10月31日。其中蕴含的思想在后来发展出了区块链。 白皮书很值得一看,是这个领域经典中的经典。这篇笔记从比特币白皮书中的视角开始展开,加入了一些对比特币网络相关实现细节的理解。 1. 比特币和区块链原理 比特币方案的目标是要实现点对点的电子现金,允许在线支付直接从一方发送到另一方,无需通过金融机构作为中介。 它要在一个点对点网络,去中心化的环境中解决如下几个问题: 如何确定电子现金是谁的?你只能花自己的钱,不能花别人的。 如何防止双花(就是上面邮件中的Double Spending)?你的一笔钱只能被花一次,不能趁记账还没完成,同时发起两笔交易,把一笔钱花两次。 如何发行货币,如何驱动网络自发运转。 这些问题在有金融机构做中介时候是很容易解决的,例如: 银行给客户开通账户,记录这个客户有多少钱,并给客户配置支付密码。 客户通过自己的密码在银行做认证,证明是本人后,支付全部或部分金额。 花钱的时候,银行会实时变更客户的账户余额。这样越花越少,不存在一笔钱花了两次的可能。 货币发行和整个系统运转都是通过金融机构。 在比特币系统中: 针对第一个问题,使用传统的非对称加密机制解决。比特币系统中每一个账户都关联一个公钥,用户使用私钥签名自己的交易。系统通过验证交易的数字签名来确定交易的合法性。 针对第二个问题,比特币系统提出了一种新方法。把交易打包进区块,进行哈希摘要,按照摘要把区块链接成一个分布式的不断增长的链状结构,链状结构能让大家对账本里面的交易的先后顺序有共识(形成Single Source of Truth),如果前面有交易已经花过一笔钱了,后面的交易想要再花这同一笔钱就无法被大家认可,不能上链形成共识。这种机制在比特币白皮书中第一次被提出来,后来被称作区块链。区块打包上链的工作是所有参与者分布式竞争完成的,在比特币中,采用POW(工作量证明)做共识算法。关于POW,下文挖矿环节有详述。 针对第三个问题, 货币发行和整个交易系统的运行都是通过矿工的挖矿行为来驱动。 比特币使用公私钥系统来解决用户认证的问题。 这个方案具有很强的隐私保护能力。 举个极端的例子,一个人可以在小黑屋里,在完全离线的环境下,用笔和纸生成一对公私钥,然后用公钥导出一个比特币地址。 只要把这个地址(例如:1J7mdg5rbQyUHENYdx39AWISME7fsLpEoXZy)发布出去,他就可以接受汇款了。 入账后, 只有小黑屋里的这个人可以操作区块链上的那笔钱(因为只有他有该比特币账户对应的私钥)。如果刻意隐瞒,比特币网络上的钱很难追踪到对应的人。当然,隐私在比特币和法币的转换过程中有可能泄漏。 上图示意比特币的区块链。图中Block2的Header里面,保存着Block 1 的Header的哈希值。 哈希值起到了指向链表中父节点指针的作用。 根据上链的先后顺序,Block2中的交易比Block1中的交易晚, Block3中的交易比Block2中的交易晚。 每一个区块(Block)中记录的交易放在区块体中, 对区块体中所有交易也做一次哈希,得到的值放到区块头里面(在Merkle Root域)。 通过区块链,保证了交易的先后顺序,也保证了区块链中形成共识的交易不可篡改(因为交易的哈希也存在区块头中,改变交易的内容会导致区块头里面的Merkel Root哈希值对不上, 如果把Merkle Root的值也改了,又会导致区块头的哈希值变化,区块头哈希值变化就会让区块本身从链表里面移除)。 The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they’ll generate the longest chain and outpace attackers. — 比特币白皮书 ...

2022年4月3日 · 2 分钟 · 费小慢

关于Web 3.0

什么是NFT?什么是DAO?DeFi?GameFi?SocialFI? 区块链钱包里放的是什么? Web 3对于公司有什么机会? 对于程序员有什么机会? 美国为啥能block俄罗斯用户的加密币交易? 1. Web1, Web2,Web3 web1 主要应用是做个网站发布信息。 web2 除了web1之外多了交互, 信息的消费者也产生内容,并且被平台消费(看广告给平台产生收入)。 web3 结合了web1的去中心化和社区管理,以及web2的丰富交互功能, 最大特点是用户和开发者可以拥有内容。web3是代币驱动的。 根据 Chis Dixon @ a16z 的描述, web3是开发者和用户共同拥有的网络,由代币来驱动。 Web1 (roughly 1990-2005) was about open protocols that were decentralized and community-governed. Most of the value accrued to the edges of the network — users and builders. Web2 (roughly 2005-2020) was about siloed, centralized services run by corporations. Most of the value accrued to a handful of companies like Google, Apple, Amazon, and Facebook. Web3 is the internet owned by the builders and users, orchestrated with tokens. … combines the decentralized, community-governed ethos of web1 with the advanced, modern functionality of web2. ...

2022年3月27日 · 3 分钟 · 费小慢

《底层逻辑》中的几张图

图摘录自《底层逻辑:看清这个世界的底牌》一书。 对图的解释夹杂我自己的理解。 1. 是非对错的底层逻辑 1.1. 不要纠结 “成年人的世界不分对错,只讲利益”。 当执着于对错的时候, 本身就已经搞错方向了。不要纠结,不要纠缠。一笑了之。 法学家和经济学家是职业,为了学术研究对错。 一般人在生活中,是经济学家研究的对象,是经济学上的人,是商人。 商人如果用法学家/经济学家的视角去分析问题,就太学究了。 2. 思考问题的底层逻辑 2.1. 要素和关系 这个图是说系统的组成。 包括要素(一些列的名词概念),和各个要素之间的关系。 举个例子,商业系统中有产品,流量,转化,留存,利润等等要素。 因果链: 产品体验好, 留存增加 增强回路: 产品体验好,留存增加,利润增加,研发投入加大,产品体验更好…, 或者反向增强:产品体验不好 调节回路:流量增加到一定程度,产品体验变差 滞后效应: 流量增加到留存增加中间有滞后(转化的过程) 2.2. 逻辑推导 这个图是说,证明事实证有不证无,证有时候用举例。 表达观点,不要以偏概全。得出一般性结论只能靠证明、推理,而不能随便说,“这难道不是共识吗?”“所有人都认为……”。 2.3. 看到本质 这个图是说,要抓住本质。离写字楼近的饭馆生意好, 本质是流量,离得近流量大。 当大家都开始点外卖时候,流量变了。相应的竞争策略也需要调整。 抓不住本质,当流量变化的时候,一味在饭菜口味上,在服务上下功夫,是没有太大作用的。 书中有一个微软管理供应商的例子。 公司提供午餐和晚餐给员工。午餐吃饭的人数一般比晚餐要多,因为不是人人晚上都要加班,所以做午餐的供应商利润更高。只不过有时候,午餐做的很糟糕……试想一下,如果你是负责人,你要怎么处理?派人监督?隔一段时间换个大厨?要求他们更新菜谱? 微软想出了一个制度: 选2家供应商,一家提供午饭、另一家提供晚饭; 每3个月做满意度调查,是喜欢午饭还是晚饭? 如果喜欢晚饭的多,午饭、晚饭供应商交换; 如果连续6个月,午餐都胜出的话,更换晚饭供应商。 这个制度的核心是引入了午餐供应商和晚餐供应商的竞争机制。 3. 个体进化的底层逻辑 3.1. 能力,效率和杠杆 这个图说人生的商业模式成功由三个因素相乘的积决定。 能力: 要有时间和精力投入, 而且要有好的方法(刻意练习)。 效率: 要管理精力,有取舍,并且有好的工具。 杠杆: 借助团队的力量,科技的力量,资本的力量和个人影响力来放大。 尤其提到了个人影响力中,写作和演讲的能力很重要。 ...

2022年3月20日 · 1 分钟 · 费小慢

Front End: Vue3, Vite, Css...

Directus APP使用VUE 3, Typescript,用Vite构建。 本文记录在评估Directus APP过程中了解到的前端相关知识。 1. vite 构建工具,类似webpack。 1.1. 原理 使用了浏览器的ES6模块加载功能,所以前端可以按需加载JS模块。开发过程中不需要打包了。 <script type="module" src="./foo.js"></script> 以上 module类型加载的 foo.js 中可以再import其它的模块,也可以export。浏览器认识js中的import和export命令,会按需向后台发请求加载。 1.2. 应用 在vite.config.ts中配置开发服务器,例如下面监听8080端口,API请求发送到8055端口。 server: { port: 8080, proxy: { '^/(?!admin)': { target: process.env.API_URL ? process.env.API_URL : 'http://localhost:8055/', changeOrigin: true, }, }, fs: { allow: [searchForWorkspaceRoot(process.cwd()), '/admin/'], }, }, 2. VUE 把js/css/html组织起来的方式。 运行环境(js库)提供数据绑定,改状态就会更新显示,方便JS更新页面。 2.1. 基本概念 data 一个函数,返回组件实例的数据对象。 props 一个数组,或者对象。是父组件调用此组件时候的可配置参数。 data和props都可以在template中访问,区别是 data不是通过父组件传递的,是子组件私有的,是可读可写的。 props中的数据,都是通过父组件传递给子组件的,是只读的。 data存储组件的状态, 没有data的组件就是无状态组件。 mixin, 为了DRY,为了减少重复,把组建脚本部分相同的地方(data,props,method…)抽离出来,单独放到一个对象里面。 然后在组件中mixin进去。 setup函数, 用来替代mixin,实现Composition API。 其输入为props, context,输出是一个对象。对象的所有属性都可以在组件中访问。 2.2. 组件生命周期 ...

2022年3月19日 · 2 分钟 · 费小慢

Directus

1. Collection 一个Collection对应数据库的一张表。directus的collection里面有一些独有的概念,例如alias域, sort属性等。 1.1. alias Fields that do not map directly to an actual database column are called “alias” fields. For example, presentation fields (such as dividers and groups) and certain relational types that display data stored elsewhere (such as One-to-Many (O2M) and Many-to-Many (M2M)). O2M, M2M, M2A 等,都不会在表里面有实际的列。称其为alias域。 alias虽然在实际数据库表里面没有列,但是在direct_fields表里面有记录。例如上面pages集合的element域,是m2a类型的alias。 direct_collections表里面描述系统所有受directus管理的表,direct_fields里面描述所有表的所有属性。 通过这种非侵入的方式, 数据库中原有的表结构可以不做改动。 1.2. sort 属性 如果collection建立时候,选择了支持sort,表里面就会多一个sort列。 这一列的作用是支持在app上手工拖拽的方式调整记录的顺序。 原理很简单,表里面加了一个number类型的sort列, 当拖拽的时候,会导致该列的值根据记录的位置产生变动。 1.3. share APP的一个功能, 可以把模型中的一个记录生成一个链接,发送给其它人只读访问。 可以设置密码,有效期,以哪一个身份读取等。 ...

2022年3月13日 · 1 分钟 · 费小慢

髋驱自由泳

髋部驱动适合长距离, 相对肩部驱动更省力。 抱水 肩部放松, 整个手臂的阴面(内侧,太阳照不到的那一侧)形成推水的阻力面。 推水阻力面包括大臂内侧,小臂内侧,手掌内侧。 肘部适当弯曲差不多100度。 肩部和腋下放松,但核心(腹部)收紧,身体保持直线。 手臂姿势类似爬墙, 肘部可稍高一些,体力好的时候肘部抱到耳朵位置,体力不好时候抱低一点。 动力传递 髋部滚动和打腿来联合驱动。 髋部滚动带动打腿, 打腿进一步给滚动提供支撑(通过整个大腿,小腿和脚面压水来支撑同侧髋部上抬)。 髋部上抬,进一步通过躯干传导到上肢, 带动抱水的胳膊向后甩。 动力传递的整体感觉类似上图。 在动力传递过程中: 腹部(腹直肌,腹外斜肌)保持适度紧张很关键, 否则滚动的力量就被身体的扭曲吸收了。 发力一侧的关节(抱水侧的肩关节,打腿侧的髋关节)要放松,放松才能甩出去。陆操时候,重心要完全放到不发力的一侧。 在游进中的感觉是: 髋部滚动, 同时两腿上下交换下位置完成打腿,打腿过程中腿部基本保持笔直,两腿大腿内侧轻贴以减阻。 向上滚动的髋部通过拉动背阔肌,把抱水的胳膊向后甩, 一直甩出水面后再悠到前面,再次形成入水姿势。向后甩胳膊时候,也有身体滚动导致向内侧拉抱水侧肘部的感觉。

2022年3月12日 · 1 分钟 · 费小慢

Docker

1. Docker client通过运行在主机上的docker daemon操作image和container,registry提供image的发布和下载(类似npm和pip)。 Docker 镜像是一个特殊的只读文件系统,提供容器运行时所需的程序、库、资源、配置等文件,还包含一些为运行时准备的配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 Container是image的运行实例,是运行在宿主机上的一种特殊的进程。一个image可以有多个container。 Container是在Image基础上叠加了一个读写层,在container内部做一些写操作后,可以commit读写层生成新的image 上图省略掉了存储配置文件信息的init层。init层位于上图image和container两层之间。存放运行操作系统时候需要写的一些目录/文件。例如/etc 目录。init层commit时候不提交。 因为没必要,每次只读层运行时候都会自动生成init层内容。 1.1. commands # Image docker pull hello-world 从registry下载hello-world image docker images 查看本地有哪些image docker rmi <IMAGE ID> 删除某个image,可以只写ID的前几位 # Container docker ps -a 查看有哪些container docker run -it centos 交互式运行 docker run -d flask-hello-world 后台运行 docker start <container_name> 重启 docker rm <CONTAINER ID> 删除container docker rm $(docker container ls -aq) 删除所有container docker port <CONTAINER ID> 查看端口映射信息 docker cp local_file_name 49f7960eb7e4:/docker_file_name 拷贝文件到docker里面 docker attach <container name> 对于docker run/start 启动的容器,可以用这个来attach到控制台上看输出。 docker exec -it directus-sqlite /bin/sh 在名字是 directus-sqlite的容器内执行sh 1.2. Dockerfile 是用来build image的配置文件,从一个基础image开始,运行一些列配置命令,得到另外一个image ...

2022年3月8日 · 5 分钟 · 费小慢

单元测试,测试驱动开发

1. 简介 下图出自 Learning Curves (for different programming languages) 虽然文章调侃为主, 但也看出来作者对单元测试的态度。 对Python程序员来讲,随着经验的增长, 在掌握了单元测试之后, 个人的生产力得到一个突变(至于掌握装饰器,会自我感觉膨胀,但效率提升不明显)。 很多年前刚走出校门,还是C++年代。 有个前北电的Java大牛布道,给开发团队推荐cpp-unit,说单元测试 “可以显著提高单兵作战能力”。斗转星移,南征北战,北电的Eddie已失去联系,但他的布道显然成功了。我在不同的项目中实践过单元测试: cpp unit junit luaunit python unittest jest 虽然语言不同,但unittest思想都一样,都有setup,teardown,testcase,assert/expect,mock这些概念。 对单元测试“提高单兵作战能力”的说法,我“不能认同更多”。 2. 单元测试的好处 unittest的好处主要在两个方面。 2.1. 鼓励先设计接口 要测试驱动,就要先想怎么测,从而促进在很早期就从接口定义的角度考虑问题。也促进了模块的低耦合高内聚。 另外,还带来一个额外的好处,TDD也会沉淀出类似文档的测试用例。若干年后回顾一个软件时候,看看用例,基本也了解当时的思路了。 2.2. 跑一遍测试的成本低 测试用例的写法规范,测试框架支持方便的执行和反馈结果,这样跑一遍测试没有时间和精力的负担,随时跑。 经常跑测试,持续的集成,有问题也能早暴露。 接口稳定后,有测试用例做质量保障,做重构也方便。 3. 举个例子 假如在开发一个API服务器。 做单元测试从粗到细可以有几个不同的粒度: 从API层面,用http client模拟请求,然后assert返回的结果是否符合预期 从Handler层面,模拟http请求的header,params,body等,然后喂给Handler,看返回的结构是否符合预期 模块层面,如果Handler之下还有其它业务逻辑模块,可以针对模块接口做单元测试 以上 1, 已经可以看作系统测试(端到端测试)了。 2相对于1有一些额外的好处:一般情况下,对应一个请求的处理,除了Hanlder之外,还有一些中间件来做预处理。 对于1来说,必须把中间件的功能和Handler本身作为一个整体测试。 不够灵活。 2相对于1又有一些缺点,需要Mock。 根据所用framework不同,需要mock输入输出的数据结构。 好在一般情况下,mock都比较简单,有很多framework也有第三方做好的mock库。 例如,下面对JS express 的Handler测试: import { posthandler } from '../src/handlers/post'; import { getMockReq, getMockRes } from '@jest-mock/express'; // generate a mocked response and next function, with provided values const { res, next } = getMockRes({ }) test('check post handler returns token in JSON body', async () => { // generate a mock request with params const req = getMockReq({ params: { id: 'abc-def' }, headers:{authorization:'this is my token'} }) // provide the mock req, res, and next to assert await posthandler(req, res) expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ authorization: 'this is my token', }), ) }) 引入的第三方jest-mock/express,可以帮助来生成输入数据,检查输出数据。see? easy. ...

2022年3月6日 · 1 分钟 · 费小慢

一个简单的API Proxy

1. 起因 在配置Directus使用钉钉扫码登录时候,发现钉钉的免密登录(OAuth 2)和RFC规范不一致。 需要做协议转换后才能和Directus正常通信。 需求比较小众,没有现成的软件,只好自己动手了。 2. 主要功能 能作为API通信的中间人, 转发客户端和API服务器之间的通信, 记录LOG,方便分析协议; 作为中间人,能修改请求的内容, 修改响应的内容;可以做协议适配,转换。 APIPROXY is a RESTFUL API proxy, monitor and adaptor. Forward RESTFUL API to another host. It’s man in the middle who can monitor and modify the header and body of the API Request & Response. Good for protocol study and adaptation. Features API proxy: forward any incoming API to remote sever and return the response back to client. API monitor: you can get detailed log of the API req and res in the log file. API adpator: modify the request and response on the fly while forwarding, including parameters, body, http headers etc. API mock server: you can add your own API for testing purpose easily. 3. 目前状态 初步实现了对钉钉OAuth2协议的RFC6749兼容封装。 目前Directus已经可以通过它的翻译支持钉钉免密登录。 ...

2022年3月5日 · 1 分钟 · 费小慢