MPT(Merkle Patricia Tries)是以太坊中存储区块数据的核心数据结构,它是 Merkle Tree 和 Patricia Tree 融合一个树形结构,理解 MPT 结构对之后学习以太坊区块 header 以及智能合约状态存储结构的模块源码很有帮助。 首先来看下 Merkle 树: 它的叶子是数据块的 hash,从图中可以看出非叶子节点是其子节点串联字符串的 hash,底层数据的任何变动都会影响父节点,这棵树的 Merkle Root 代表对底层所有数据的“摘要”。 这样的树有一个很大的好处,比如我们把交易信息写入这样的树形结构,当需要证明一个交易是否存在这颗树中的时候,就不需要重新计算所有交易的 hash 值。比如证明图中 Hash 1-1,我们可以借助 Hash 1-0 重新计算出 Hash 1,然后再借助 Hash 0 重新计算出 Top Hash,这样就可以根据算出来的 Top Hash 和原来的 Top Hash 是否一样,如果一样的话那么 Hash 1-1 就属于这棵树。 所以想象一下,我们将这个 Top Hash 储存在区块头中,那么有了区块头就可以对区块信息进行验证了。同时 Hash 计算的过程可以十分快速,预处理可以在短时间内完成。利用 Merkle 树结构能带来巨大的比较性能提升。 再来看下 Patricia 树: 从它的名字压缩前缀树再结合上图就可以猜出来 Patricia 树的特点了,这种树形结构比将每一个字符作为一个节点的普通 trie 树形结构,它的键值可以使用多个字符,降低了树的高度,也节省了空间,再看个例子: 图中可以很容易看出数中所存储的键值对: 6c0a5c71ec20bq3w => 5 6c0a5c71ec20CX7j => 27 6c0a5c71781a1FXq => 18 6c0a5c71781a9Dog => 64 6c0a8f743b95zUfe => 30 6c0a8f743b95jx5R => 2 6c0a8f740d16y03G => 43 6c0a8f740d16vcc1 => 48 以太坊中的 MPT: 在以太坊中 MPT 的节点的规格主要有一下几个:

阅读全文

RLP(Recursive Length Prefix),递归长度前缀编码,它是以太坊序列化所采用的序列化和反序列化的主要方式。区块、交易等数据结构在 网络传输和持久化时会先经过RLP编码后再存储到数据库中。rlp适用于任意的二进制数据数组的编码,在以太坊中,rpl接受的数据分为两类:1.字节数组 2.类list数据结构。 以太坊中rlp的具体定义和规则我们可以在黄皮书中找到(Appendix B. Recursive Length Prefix): * 序列化定义 * O 所有byte的集合 * B 所有可能字节数组 * L 不只单一节点的树形结构(比如结构体或者树节点分支节点,非叶子节点) * T 所有字节数组的树形结构组合 序列化处理 通过两个子函数定义RLP分别处理上面说的两种数据类型 Rb(x)字节数组序列化处理规则 如果字节数组只包含一个字节(对于 [0x00, 0x7f] 范围内的单个字节),而且这个字节的大小小于128,那么不对数据进行处理,处理结果就是原数据,比如:a的编码是97。 如果字节数组的长度小于56,那么处理结果就等于在原始数据前面加上(128+字节数据的长度)的前缀,比如abc编码结果是131 97 98 99,其中131=128+len(“abc”),97 98 99依次是a b c。 如果不是上面两种情况,那么处理结果就等于在原始数据前面加上原始数据长度的大端表示,然后在前面加上(183 + 原始数据大端表示的长度),比如编码下面这段字符串The length of this sentence is more than 55 bytes, I know it because I pre-designed it编码结果如下184 86 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116,其中前三个字节的计算方式如下:1.

阅读全文

这里假设已经存在bootnodes服务节点了,需要给私有网络中添加一个以太坊节点,用docker来部署比较方便,不用手动去添加。这里只是写了个简单的demo,并不是一个完整基于bootnodes来部署以太坊私有网络的方法。 当然创建一个bootnode也很简单,大致步骤如下: bootnode -genkey boot.key bootnode -nodekey boot.key -verbosity 9 -addr :30310 如果需要一个完整的基于docker的一台私有网络,可以通过docker-compose将bootnode和节点来编排,后面有空来完善一下。 具体源码 => https://github.com/yuansir/bootnodes-ethereum-peer-docker Usage genesis.json 复制bootnodes链接所有节点的共同 genesis.json 文件 Dockerfile 根据需求修改 Dockerfile 中的环境变量 build image 比如tag为 eth-peer:1.0 docker build -t eth-peer:1.0 . run // docker run -it --rm --name your-name eth-peer:1.0 /bin/bash docker run -d --name your-name eth-peer:1.0 补充 如果出现以下错误: System clock seems off by -XXXXXs, which can prevent network connectivity Please enable network time synchronisation in system settings 主要是由于该节点r容器的时间和bootnodes服务节点时间不一致,如果bootnodes就是当前节点容器的宿主机,可以在docker run 时加上 -v /etc/localtime:/etc/localtime:ro,如果bootnodes服务节点和该节点坊容器不在同一主机,可以通过将两者的时间同一至同一时区的网络时间。

阅读全文

Go实践微服务 -- 服务发现

服务的注册发现对于微服务来说是一个非常重要的环节,在单一架构应用中,service之间的互相调用,通过一个固定的host和port来发起REST或者RPC来调用,但是在微服务架构中,各个服务往往是动态变化的,所以需要一个服务发现机制来发送客户端的请求到动态的service实例中去。 在利用go micro来实现服务发现便利很多,micro中默认支持使用 Consul 来做服务发现,当然它使用插件机制(go-plugins)还支持 Etcd, Gossip, NATS等其他的第三方服务注册发现工具。在每个服务启动的时候,都将自己注册到registry上,退出时也自动解注册,具体实现我们可以来看一下go-micro/service.go的相关代码片段: ...... func (s *service) run(exit chan bool) { if s.opts.RegisterInterval <= time.Duration(0) { return } //定时注册自己 t := time.NewTicker(s.opts.RegisterInterval) for { select { case <-t.C: err := s.opts.Server.Register() if err != nil { log.Log("service run Server.Register error: ", err) } case <-exit: t.Stop() return } } } ...... func (s *service) Start() error { for _, fn := range s.opts.BeforeStart { if err := fn(); err !

阅读全文

ERC721 官方简介是:A standard interface for non-fungible tokens, also known as deeds.也叫非同质代币,或者不可置换代币(NFTs)。提到ERC721,一个好理解的例子就是CryptoKitties 迷恋猫,每一只猫都是独一无二的拥有不同基因,有收藏价值属性。ERC721对于虚拟资产收藏品领域会有很好的应用价值和市场需求。 它和我写的上一篇《OpenZeppelin ERC20源码分析》介绍的ERC20有所不同,ERC721最小的单位为1无法再分割,代表独一无二的,针对不可置换的Token的智能合约标准接口。从 ERC721标准草案中可以看到,兼容ERC20的方法有4个:name,symbol,totalSupply,balanceOf 添加的新方法:ownerOf,takeOwnership ERC721还重写了approve和transfer。 分析OpenZeppelin ERC721源码前同样我画了一个继承和调用关系的思维导图,可以帮助更容易地看源码。 ERC721Basic.sol pragma solidity ^0.4.23; /** * @title ERC721 标准的基本接口 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Basic { event Transfer( address indexed _from, address indexed _to, uint256 _tokenId ); event Approval( address indexed _owner, address indexed _approved, uint256 _tokenId ); event ApprovalForAll( address indexed _owner, address indexed _operator, bool _approved ); function balanceOf(address _owner) public view returns (uint256 _balance); function ownerOf(uint256 _tokenId) public view returns (address _owner); function exists(uint256 _tokenId) public view returns (bool _exists); function approve(address _to, uint256 _tokenId) public; function getApproved(uint256 _tokenId) public view returns (address _operator); function setApprovalForAll(address _operator, bool _approved) public; function isApprovedForAll(address _owner, address _operator) public view returns (bool); function transferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public; } ERC721Basic 合约定义了基本的接口方法:

阅读全文

ERC20:Ethereum Request for Comments 20,是一个基于以太坊代币的接口标准(协议)。所有符合ERC-20标准的代币都能立即兼容以太坊钱包,它能让用户和交易所,都能非常方便的管理多种代币,转账、存储、ICO等等。 OpenZeppelin的Token中实现了ERC20的一个安全的合约代码,本篇主要来分析一下源码,了解一下ERC20的实现,由于代码之间的调用可能略复杂,直接每个文件每个文件的来看会有点绕,我直接画了一个继承和调用关系的思维导图,可以帮助更容易地看源码。 ERC20Basic.sol pragma solidity ^0.4.23; contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } ERC20Basic合约主要定义了ERC20的基本接口,定义了必须要实现的方法: totalSupply 返回总共发行量 balanceOf 查询指定address的余额 transfer 发送指定数目的token到指定账户,同时发送后需要触发Transfer事件 Transfer事件,任何token发送发生时,必须触发该事件,即使是0额度。 当一个token合约创建时,应该触发一个Transfer事件,token的发送方是0x0,也就是说凭空而来的token,简称空气币。 ERC20.sol pragma solidity ^0.4.23; import "./ERC20Basic.sol"; contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval( address indexed owner, address indexed spender, uint256 value ); } ERC20合约继承了ERC20Basic,另外定义了approve相关的方法:

阅读全文

在之前介绍了《Go实践微服务 – gRPC配置和使用》,通过gRPC直接编写微服务有点麻烦,比如都需要手动指定服务地址和端口,不便于管理,也需要自己来实现服务注册和发现的逻辑,同时对于网关以及监控等都是微服务架构和实施中不可避免的。所以引入 go-micro,go-micro是一个插件式的RPC框架,它是Micro生态的一部分,Micro是一个简化分布式开发的微服务生态系统,它为开发分布式应用程序提供了基本的构建模块。 Go Micro抽象出分布式系统的细节。以下是主要功能。 服务发现 - 通过服务发现自动注册和名称解析 负载平衡 - 基于发现构建的服务的智能客户端负载平衡 同步通信 - 基于RPC的通信,支持双向流 异步通信 - 为事件驱动架构内置的Pub/Sub接口 消息编码 - 基于带有protobuf和json的内容类型的动态编码 服务接口 - 所有功能都打包在一个简单的高级界面中,用于开发微服务 通过写一个船运的示例看下go-micro如何来写一个微服务。 安装protoc-gen-micro go get github.com/micro/protoc-gen-micro 编写proto文件来定义服务 consignment-service/proto/consignment/consignment.proto syntax = "proto3"; package go.micro.srv.consignment; // 定义货轮服务 service ShippingService { // 托运一批货物 rpc CreateConsignment (Consignment) returns (Response) { } // 查看货物信息 rpc GetConsignments (GetRequest) returns (Response) { } } // 货物信息 message Consignment { string id = 1; // ID string description = 2; // 描述 int32 weight = 3; // 重量 repeated Container containers = 4; // 集装箱,多个 string vessel_id = 5; // 承运货轮ID } // 集装箱 message Container { string id = 1; // 编号 string customer_id = 2; // 客户ID string origin = 3; // 出发地 string user_id = 4; // 集装箱所属用户ID } // 托运结果 message Response { bool created = 1; // 托运结果 Consignment consignment = 2; // 新托运的货物 repeated Consignment consignments = 3; // 所有托运的货物 } message GetRequest { } 生成 gRPC 代码 consignment-service/Makefile

阅读全文

gRPC是一个高性能、开源和通用的RPC框架,面向移动和HTTP/2 设计。 它有很多特点,如双向流、流控、头部压缩、单TCP连接上的多复用请求,节省带宽、降低TCP链接次数、节省CPU使用和延长电池寿命等。强大的IDL,默认采用Protocol Buffers数据序列化协议,支持多种开发语言以及移动端。gRPC既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现客户端和服务器端的通信和简化通信系统的构建,很容易去构建分布式应用和微服务架构模式。 各大主流的RPC框架的性能比较可以参考这一篇《流行的rpc框架benchmark 2018新春版》 我很久前写过一篇《Thrift 应用总结》 安装protobuf编译器 官方推荐使用protobuf 3,我用的是Mac,所以可以直接用brew安装 3.X 版本 brew info protobuf brew install protobuf 安装完可以看下版本号 protoc --version libprotoc 3.5.1 当然也可以通过编译安装 #去下载Protocol Buffers v3.5.1的release包 brew info automake brew info libtool # 没有这两个就安装 ./autogen.sh # 检查没问题了 ./configure make -j8 make check make install 安装Golang for protobuf插件 go get -u -v github.com/golang/protobuf/proto go get -u -v github.com/golang/protobuf/protoc-gen-go 安装grpc-go go get -u -v google.golang.org/grpc 由于墙的原因,可能你会看到下面的错误:

阅读全文

Go 实现依赖注入

为什么需要依赖注入 作为一名软件开发人员,如果我们希望使代码保持清洁和可维护性,我们将代码分割成不同的层次。 通常边界至少放置在基础设施和业务逻辑之间。当我们专门处理复杂的业务逻辑时,基础架构依赖于我们的业务逻辑是可取的,这样在更改基础架构时不会破坏我们的软件。 开发一个新的软件项目时的第一个决定是否选择一个结构来实现分层。 大多数情况下,我选择简洁的架构,但您有另一个很好的选择,例如Domain Driven Design(领域驱动设计)。 独立于您选择的架构,我们必须粘贴来自不同模块以提供新功能,而这正是依赖注入所发挥的作用。 如何实现依赖注入 理解依赖注入,没有比示例更好的了。 想象一下,我们必须为我们想象中的项目实现用户注册,并且我们将用简约的架构来实现这一点。 我们可以想出一个像这样的实体: package entity //User结构体 type User struct { email string password string name string dateOfBirth string } //业务级别的验证 func Validate() error { ... } 现在我们来创建一个用户注册的用例: package usecase import "entity" func RegisterUser(user entity.User) { //验证和保存用户逻辑 } 看起来我们需要一些基础设施模块来存储用户。 我们还没有在基础设施级别实现任何UserRepository,我们还遇到了另一个问题,通过简洁的架构原则,更高级别的层无法知道低级别层的存在。 如何来解决这个问题? 不需要触及基础设施中的任何部分,我们会对我们的用例说,有某种用户保护程序可以帮助我们的用户注册。 package usecase import "entity" type UserSaver interface { Save(user entity.User) error } //UserRegisterUseCase 存储它的依赖关系 type UserRegisterUseCase struct { userSaver UserSaver } func newUserRegisterUseCase(userSaver UserSaver) UserRegisterUseCase { return &{userSaver} } func (u UserRegisterUseCase) RegisterUser(user entity.

阅读全文

以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。 Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。 func (s *Ethereum) StartMining(local bool) error { eb, err := s.Etherbase()//用户地址 if err != nil { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } if clique, ok := s.engine.(*clique.Clique); ok { //如果是clique共识算法 wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根据用它胡地址获取wallet对象 if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) return fmt.

阅读全文

作者的图片

Ryan是菜鸟 | LNMP技术栈笔记

一步一个脚印,一直在路上!记录LNMP技术栈,Web架构,区块链等笔记

菜鸟码农

南京