1.Protobuf简介protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多。虽然是二进制数据格式,但并没有因此变得复杂,开发人员通过按照一定的语法定义结构化的消息格式,然后送给命令行工具,工具将自动生成相关的类,可以支持php、java、c++、python等语言环境。通过将这些类包含在项目中,可以很轻松的调用相关方法来完成业务消息的序列化与反序列化工作。protobuf在google中是一个比较核心的基础库,作为分布式运算涉及到大量的不同业务消息的传递,如何高效简洁的表示、操作这些业务消息在google这样的大规模应用中是至关重要的。而protobuf这样的库正好是在效率、数据大小、易用性之间取得了很好的平衡。官方文档http://code.google.com/p/protobuf/2.Protobuf如何工作你首先需要在一个.proto文件中定义你需要做串行化的数据结构信息。每个ProtocolBuffer信息是一小段逻辑记录,包含一系列的键值对。这里有个非常简单的.proto文件定义了个人信息:messagePerson{requiredstringname=1;requiredint32id=2;optionalstringemail=3;enumPhoneType{MOBILE=0;HOME=1;WORK=2;}messagePhoneNumber{requiredstringnumber=1;optionalPhoneTypetype=2[default=HOME];}repeatedPhoneNumberphone=4;}有如你所见,消息格式很简单,每个消息类型拥有一个或多个特定的数字字段,每个字段拥有一个名字和一个值类型。值类型可以是数字(整数或浮点)、布尔型、字符串、原始字节或者其他ProtocolBuffer类型,还允许数据结构的分级。你可以指定可选字段,必选字段和重复字段。你可以在(http://code.google.com/apis/protocolbuffers/docs/proto.html)找到更多关于如何编写.proto文件的信息。一旦你定义了自己的报文格式(message),你就可以运行ProtocolBuffer编译器,将你的.proto文件编译成特定语言的类。这些类提供了简单的方法访问每个字段(像是query()和set_query()),像是访问类的方法一样将结构串行化或反串行化。例如你可以选择C++语言,运行编译如上的协议文件生成类叫做Person。随后你就可以在应用中使用这个类来串行化的读取报文信息。你可以这么写代码:Personperson;person.set_name("JohnDoe");person.set_id(1234);person.set_email("jdoe@example.com");fstream.output("myfile",ios::out|ios::binary);person.SerializeToOstream(&output);然后,你可以读取报文中的数据:fstreaminput("myfile",ios::in|ios:binary);Personperson;person.ParseFromIstream(&input);cout<<"Name:"<<person.name()<<endl;cout<<"E-mail:"<<person.email()<<endl;你可以在不影响向后兼容的情况下随意给数据结构增加字段,旧有的数据会忽略新的字段。所以如果使用ProtocolBuffer作为通信协议,你可以无须担心破坏现有代码的情况下扩展协议。你可以在API参考(http://code.google.com/apis/protocolbuffers/docs/reference/overview.html)中找到完整的参考,而关于ProtocolBuffer的报文格式编码则可以在(http://code.google.com/apis/protocolbuffers/docs/encoding.html)中找到。3.Protobuf消息定义要通信,必须有协议,否则双方无法理解对方的码流。在protobuf中,协议是由一系列的消息组成的。因此最重要的就是定义通信时使用到的消息格式。消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。字段格式:限定修饰符①|数据类型②|字段名称③|=|字段编码值④|[字段默认值⑤]①.限定修饰符包含required\optional\repeatedRequired:表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。Optional:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。---因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。Repeated:表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。②.数据类型Protobuf定义了一套基本数据类型。几乎都可以映射到C++\Java等语言的基础数据类型.protobuf数据类型描述打包C++语言映射bool布尔类型1字节booldouble64位浮点数Ndoublefloat32为浮点数Nfloatint3232位整数、Nintuin32无符号32位整数Nunsignedintint6464位整数N__int64uint6464为无符号整Nunsigned__int64sint3232位整数,处理负数效率更高Nint32sing6464位整数处理负数效率更高N__int64fixed3232位无符号整数4unsignedint32fixed6464位无符号整数8unsigned__int64sfixed3232位整数、能以更高的效率处理负数4unsignedint32sfixed6464为整数8unsigned__int64string只能处理ASCII字符Nstd::stringbytes用于处理多字节的语言字符、如中文Nstd::stringenum可以包含一个用户自定义的枚举类型uint32N(uint32)enummessage可以包含一个用户自定义的消息类型NobjectofclassN表示打包的字节并不是固定。而是根据数据的大小或者长度。例如int32,如果数值比较小,在0~127时,使用一个字节打包。关于枚举的打包方式和uint32相同。关于message,类似于C语言中的结构包含另外一个结构作为数据成员一样。关于fixed32和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.③.字段名称字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的。protobuf建议字段的命名采用以下划线分割的驼峰式。例如first_name而不是firstName.④.字段编码值有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。编码值的取值范围为1~2^32(4294967296)。其中1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16.1900~2000编码值为Googleprotobuf系统内部保留值,建议不要在自己的项目中使用。protobuf还建议把经常要传递的值把其字段编码设置为1-15之间的值。消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不实用required。如果使用了required,需要全网统一升级,如果使用optional或者repeated可以平滑升级。⑤.默认值。当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。当接受数据是,对于optional字段,如果没有接收到optional字段,则设置为默认值。关于importprotobuf接口文件可以像C语言的h文件一个,分离为多个,在需要的时候通过import导入需要对文件。其行为和C语言的#include或者java的import的行为大致相同。关于package避免名称冲突,可以给每个文件指定一个package名称,对于java解析为java中的包。对于C++则解析为名称空间。关于message支持嵌套消息,消息可以包含另一个消息作为其字段。也可以在消息内定义一个新的消息。关于enum枚举的定义和C++相同,但是有一些限制。枚举值必须大于等于0的整数。使用分号(;)分隔枚举变量而不是C++语言中的逗号(,)eg.enumVoipProtocol{H323=1;SIP=2;MGCP=3;H248=4;}
https://github.com/tendermint/tendermint/blob/master/docs/introduction/quick-start.md参考以上github官方文档1、部署环境,编译安装tendermint官方快捷脚本(要fq)curl-Lhttps://git.io/fFfOR|bashsource~/.profile方便无法翻墙,复制如下#!/usr/bin/envbash#XXX:thisscriptismeanttobeusedonlyonafreshUbuntu16.04instance#andhasonlybeentestedonDigitalOcean#getandunpackgolangcurl-Ohttps://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gztar-xvfgo1.10.linux-amd64.tar.gzaptinstallmake##movegoandaddbinarytopathmvgo/usr/localecho"exportPATH=\$PATH:/usr/local/go/bin">>~/.profile##createtheGOPATHdirectory,setGOPATHandputonPATHmkdirgoAppsecho"exportGOPATH=/root/goApps">>~/.profileecho"exportPATH=\$PATH:\$GOPATH/bin">>~/.profilesource~/.profile##getthecodeandmoveintoitREPO=github.com/tendermint/tendermintgoget$REPOcd$GOPATH/src/$REPO##buildgitcheckoutmastermakeget_toolsmakeget_vendor_depsmakeinstall2、单节点启动初始化节点,会在~/.tendermint目录下生成节点配置文件,包括公私钥、config文件以及genesis文件,也可以用--home参数自己指定目录tendermintinit启动节点--proxy_app参数指定上层server应用,kvstore是官方自带的键值对存储应用tendermintnode--proxy_app=kvstore发送交易测试发送交易:curl-s'localhost:26657/broadcast_tx_commit?tx="abcd"'交易查询:curl-s'localhost:26657/abci_query?data="abcd"'发起自定义键值对交易:curl-s'localhost:26657/broadcast_tx_commit?tx="name=satoshi"'通过key查询交易:curl-s'localhost:26657/abci_query?data="name"'3、单机多节点启动(疯狂踩坑)1、先总结坑github给出的操作方式是多机的,因为要修改各个节点ip网上有用docker做的,但是我不会用,写完文档就去学习docker去。。。单机情况下各个节点的p2p和rpc监听端口是冲突的,要修改参考了https://blog.csdn.net/weixin_37504041/article/details/92798787我一开始根据它这个做的,但是在添加验证节点时,在kvsore应用等待链接窗口出现验证节点链接后秒退的情况,我是用abci-clikvstore命令手动打开的kvsore应用,然后让节点一个一个链接的,和官方给的文档对比,官方是在启动节点时通过--proxy_app命令链接的,猜想可能是因为这个。另外还有一个问题,可能会碰到不能链接到server的问题,网上看到有人自己写的server,出现端口和配置文件不匹配的问题,我自己遇到的问题是因为kvsore未启动,所以无法链接。然后我手启遇到上面碰到的问题,最后还是参照官方做的,过程如下。2、单机多节点实验步骤总结1、官方给出了测试节点,初始化方法:tenderminttestnet2、获取各个节点id,记到文档里,会用到。tendermintshow_node_id--home./mytestnet/node0tendermintshow_node_id--home./mytestnet/node1tendermintshow_node_id--home./mytestnet/node2tendermintshow_node_id--home./mytestnet/node33、官方文档这里因为是多机,所以执行了以下命令tendermintnode--home./mytestnet/node0--proxy_app=kvstore--p2p.persistent_peers="ID1@IP1:26656,ID2@IP2:26656,ID3@IP3:26656,ID4@IP4:26656"tendermintnode--home./mytestnet/node1--proxy_app=kvstore--p2p.persistent_peers="ID1@IP1:26656,ID2@IP2:26656,ID3@IP3:26656,ID4@IP4:26656"tendermintnode--home./mytestnet/node2--proxy_app=kvstore--p2p.persistent_peers="ID1@IP1:26656,ID2@IP2:26656,ID3@IP3:26656,ID4@IP4:26656"tendermintnode--home./mytestnet/node3--proxy_app=kvstore--p2p.persistent_peers="ID1@IP1:26656,ID2@IP2:26656,ID3@IP3:26656,ID4@IP4:26656"如果你多机执行,就把id换成我们记下每个node的对应id,和该node对应的ip即可3、但是我们穷人只有一台电脑重点来了访问~/mytestnet,可以看到4个node的四个配置文档,四个都需要改,我们以一个为例访问./node1/config,编辑config.toml因为node0的rpc端口为26657,p2p端口为26656,所以我们把node1的rpc端口为36657,p2p端口为36656.同理把node2的rpc端口为46657,p2p端口为46656,把node3的rpc端口为56657,p2p端口为56656[rpc]laddr=“tcp://127.0.0.1:36657”[p2p]laddr=“tcp://0.0.0.0:36656”然后我们看一下persistent_peers这个配置选项,它配置的是peer节点,官方给的这个是根据它这个测试忘这4个节点初始化好的,如果是自己搭建是没有初始化的,我们需要自己添加。现在看下官方给的这个,@符号前面的node的id是它初始化好的四个节点id,不需要修改,@后面的node1:26656我们要改成127.0.0.1:36656,对应node1的ip:p2p端口,因为我们是单节点验证,所以ip都是127.0.0.1,P2P端口就分别是26656,36656,46656,56656.至此,实验环境配置完成,tendermintnode--home./mytestnet/node0--proxy_app=kvstoretendermintnode--home./mytestnet/node1--proxy_app=kvstoretendermintnode--home./mytestnet/node2--proxy_app=kvstoretendermintnode--home./mytestnet/node3--proxy_app=kvstore启动4个节点,即可进行测试。根据拜占庭共识,3f+1个节点可以运行f个恶意节点,所以在1个节点恶意情况下,可以完成共识出块。交易命令同上文单节点学习docker去了,告辞。
当一个Tx进来时,Tmcore的mempool(MP)会通过mempoolconnection(一个socket连接,由abci-server提供,端口号为46658)调用ApplicationLogic(AL:也就是abci-app,我们自己用任何语言编写的APP逻辑)里的checkTx方法,AL向MP返回验证结果。MP根据验证结果放行或者拒绝该Tx。Tendermint(TM)把tx暂存在内存池(mempool)里,并把这条tx通过P2P网络复制给其它TM节点。TM发起了对这条tx的拜占庭共识投票,所有4个Tendermint节点都参与了。投票过程分三轮,第一轮预投票(PreVote),超过2/3认可后进入第二轮预提交(PreCommit),超过2/3认可后进入最后一轮正式提交(Commit)TM提交Tx时依次通过ConsensusConnection(一个socket连接,由abci-server提供,端口号为46658)向ABCI-APP发送指令BeginBlock-->多次DeliverTx-->EndBlock-->Commit,提交成功后会将StateRoot(applicationMerkleroothash)返回给TM,TMNew出一个区块。
1.摘要Tendermint是跨链Cosmos项目的核心技术。本文主要介绍以下内容:(1)Tendermint的网络层级框架图(2)Tendermint模块组成及共识算法原理(3)Tendermint工作流程2.Tendermint概述Cosmos的开发团队Tendermint其实早在2014年就开始意识到了其不足,并持续专注于寻求不依赖挖矿等高电力消耗的共识机制,提供快速的交易处理能力,它们的目标是为全事件所有的区块链提供速度、安全和可扩展性。目前,Tendermint加入了微软Azure区块链即服务平台,也成为了以太坊区块链联盟成员之一,同时Tendermint也是跨链技术Cosmos的核心技术。两者大致的关系如下:图中可以轻松看出Cosmos就是在Tendermint基础上添加一些插件功能来实现的。2.1Tendermint的概念Tendermint的概念总结下有以下几点:(1)Tendermint是一个能够在不同机器上,安全一致复制应用的软件,其中安全性和一致性也是分布式账本的关键概念。(2)Tendermint具备拜占庭容错能力,是一种拜占庭容错共识算法。(3)Tendermint主要有两部分组成:1)TendermintCore:区块链共识引擎,负责节点之间数据传输以及拜占庭共识。2)ABCI:区块链应用程序接口(theApplicationBlockChainInterface),也是一个协议,支持任何语言的交易处理实现。总体来讲,Tendermint可以理解为一个模块化的区块链软件框架,支持开发者个性化定制自己的区块链,而又不需要考虑共识以及网络传输的实现。2.2Tendermint设计原则区块链是一个具备确定性的状态机,可以在不信任的节点之间进行状态复制,包括应用的状态和改变状态的交易。从架构的层面上,区块链可以简单分为三个概念层:(1)网络层(Networking):负责交易和数据传输和同步。(2)共识算法(Consensus):负责不同的验证节点处理完交易后,保证状态的一致,也就是将交易打包到区块中。(3)应用程序(Application):交易的真正执行者。大致框架如下:目前大部分的区块链实现都是采用上面的框架,实现成单一的程序,但是这就很容易出现两个问题:(1)代码复用困难,代码库的分支管理变得复杂。(2)限制了应用开发的语言。如何去规避这两个问题呢?Tendermint设计了自己的一套框架,其设计原则是易使用,易理解,高性能,适用于各种分布式应用。它的创新之处在于,将区块链应用(状态)与底层共识进行了分离,将共识引擎和P2P网络层封装组成TendermintCore。同时提供ABCI接口与应用层进行交互,应用逻辑可以用任何语言编写,应用做的事情实际上就是状态机控制。基于这种架构,应用的开发者可以方便地实现自己的区块链。Tendermint的框架总体来讲分为ABCIApplication以及TendermintCore两部分,两者通过ABCI连接。3.Tendermint核心模块3.1ABCIApplication开发者定制开发的区块链应用,开发语言不受限制,可以使用任何语言进行开发,但是必须实现为一个ABCIServer,即需要满足以下几点:(1)是一个SocketServer,需支持TSP或GRPC两种方式之一。(2)能够处理ABCIMessage。所有的ABCI消息类型都是通过protobuf来定义的,具体的消息格式可参考https://github.com/tendermint/abci/blob/master/types/types.proto(3)实现区块链应用接口(ABCI)。ABCI是Tendermint中定义的一套Application与TendermintCore之间交互的协议。详细定义如下(版本:0.10.3):ABCI接口可以分为三类:信息查询、交易校验以及共识相关处理。而TendermintCore作为ABCIClient在启动时,会与ABCIServer建立三个连接,分别用于这三类接口消息的处理。在TendermintCore与Application交互的所有消息类型中,有3种主要的消息类型:(1)CheckTx消息用于验证交易。TendermintCore中的mempool通过此消息校验交易的合法性,通过之后才会将交易广播给其它节点。(2)DeliverTx消息是应用的主要工作流程,通过此消息真正执行交易,包括验证交易、更新应用程序的状态。(3)Commit消息通知应用程序计算当前的事件状态,并存在下一区块头中。3.2TendermintCoreTendermint共识引擎,包含区块链需要大部分功能实现,主要有:共识算法:拜占庭POS算法。P2P:采用gossip算法,默认端口是46656。RPC:区块链对外接口,默认端口是46657。支持三种访问方式:URIoverHTTP、JSONRPCoverHTTP、JSONRPCoverwebsockets。详细的RPC接口定义列表可以参考https://tendermint.github.io/slate其它:交易缓存池、消息队列等。3.2.1共识算法Tendermint是一个易于理解的BFT共识协议。协议遵循一个简单的状态机,如下:协议中有两个角色:(1)验证人:协议中的角色或者节点,不同的验证者在投票过程中具备不同的权力(votepower)。(2)提议人:由验证人轮流产生。验证人轮流对交易的区块提议并对提议的区块投票。区块被提交到链上,且每个区块就是一个区块高度。但区块也有可能提交失败,这种情况下协议将选择下一个验证人在相同高度上提议一个新块,重新开始投票。从图中可以看到,成功提交一个区块,必须经过两阶段的投票,称为pre-vote和pre-commit。当超过2/3的验证人在同一轮提议中对同一个块进行了pre-commit投票,那么这个区块才会被提交。由于离线或者网络延迟等原因,可能造成提议人提议区块失败。这种情况在Tendermint中也是允许的,因为验证人会在进入下一轮提议之前等待一定时间,用于接收提议人提议的区块。假设少于三分之一的验证人是拜占庭节点,Tendermint能够保证验证人永远不会在同一高度重复提交区块而造成冲突。为了做到这一点,Tendermint引入了锁定机制,一旦验证人预投票了一个区块,那么该验证人就会被锁定在这个区块。然后:(1)该验证人必须在预提交的区块进行预投票。(2)当前一轮预提议和预投票没成功提交区块时,该验证人就会被解锁,然后进行对新块的下一轮预提交。可以看到,Tendermint共识算法和PBFT时非常相似的,可以说是PBFT的变种,那我们来比较一下:(1)相同点:1)同属BFT体系。2)抗1/3拜占庭节点攻击。3)三阶段提交,第一阶段广播交易(区块),后两阶段广播签名(确认)。4)两者都需要达到法定人数才能提交块。(2)不同点:1)Tendermint与PBFT的区别主要是在超过1/3节点为拜占庭节点的情况下。当拜占庭节点数量在验证者数量的1/3和2/3之间时,PBFT算法无法提供保证,使得攻击者可以将任意结果返回给客户端。而Tendermint共识模型认为必须超过2/3数量的precommit确认才能提交块。举个例子,如果1/2的验证者是拜占庭节点,Tendermint中这些拜占庭节点能够阻止区块的提交,但他们自己也无法提交恶意块。而在PBFT中拜占庭节点却是可以提交块给客户端。简单的说,就是比特币的网络存在分叉的可能,而Tendermint不会发生这种情况。2)另一个不同点在于拜占庭节点概念不同,PBFT指的是节点数,而Tendermint代表的是节点的权益数,也就是投票权力。3)最后一点,PBFT需要预设一组固定的验证人,而Tendermint是通过要求超过2/3法定人数的验证人员批准会员变更,从而支持验证人的动态变化。锁机制详解举个例子,有四个validator节点,A,B,C,D,在某个R轮,在propose阶段,(1)proposer节点广播出了新块blockX;(2)A的超时时间内没有收到这个新块,向外广播pre-votenil,B,C,D都收到了,向外广播pre-vote投给blockX;(3)现在四个节点进入了pre-commit阶段,A处于红色内圈,B,C,D处于蓝色外圈;(4)假设A由于自身网络不好,又没有在规定时间内收到超过2/3个对blockX的投票,于是只能发出pre-commitnil投票消息投给空块(5)D收到了B和C的pre-vote消息,加上自己的,就超过了2/3了,于是D在本机区块链里commit了blockX(6)假设此时B和C网络出现问题,收不到D在pre-commit消息,这是B和C只能看到2票投给了blockX,一票投给了空块,全部不足2/3,于是B和C都只能commit空块,高度不变,进人R+1轮,A也只能看到2票投给了blockX,一票投给了空块,也只能commit空块,高度不变,进人R+1轮;(7)在R+1轮,由于新换了一个proposer,提议了新的区块blockY,A,B,C三个个可能会在达成共识,提交blockY,于是在同样的高度,就有blockX和blockY两个块,产生了分叉?其实,Tendermint加上了锁的机制,具体就是,在第7步,即使proposer出了新块blockY,A,B,C只能被锁定在第6步他们的pre-commit块上,即A在第6步投给了空块,那么在第R+1轮,只能继续投给空块,B在第6步投给了blockX,那么在新一轮,永远只能投给blockX,C也是类似。这样在R+1轮,就会有1票投给空块,两票投给blockX,最终达成共识blockX,A,B,C三人都会commitblockX,与D一致,没有产生冲突。3.2.2P2P网络Tendermint的P2P网络协议借鉴了比特币的对等发现协议,更准确地说,Tendermint是采用了BTCD的P2P地址簿(AddressBook)机制。当连接建立后,新节点将自身的Address信息(包含IP、Port、ID等)发送给相邻节点,相邻节点接收到信息后加入到自己的地址薄,再将此条Address信息,转播给它的相邻节点。此外为了保证节点之间数据传输的安全性,Tendermint采用了基于Station-to-Station协议的认证加密方案,此协议是一种密钥协商方案,基于经典的DH算法,并提供相互密钥和实体认证。大致的流程如下:(1)每一个节点都必须生成一对ED25519密钥对作为自己的ID。(2)当两个节点建立起TCP连接时,两者都会生成一个临时的ED25519密钥对,并把临时公钥发给对方。(3)两个节点分别将自己的私钥和对方的临时公钥相乘,得到共享密钥。这个共享密钥对称加密密钥。(4)将两个临时公钥以一定规则进行排序,并将两个临时公钥拼接起来后使用Ripemd160进行哈希处理,后面填充4个0,这样可以得到一个24字节的随机数。(5)得到的随机数作为加密种子,但为了保证相同的随机数不会被相同的私钥使用两次,我们将随机数最后一个bit置为1,这样就得到了两个随机数,同时约定排序更高的公钥使用反转过的随机数来加密自己的消息,而另外一个用于解密对方节点的消息。(6)使用排序的临时公钥拼接起来,并进行SHA256哈希,得到一个挑战码。(7)每个节点都使用自己的私钥对挑战码进行签名,并将自己的公钥和签名发给其它节点校验。(8)校验通过之后,双方的认证就验证成功了。后续的通信就使用共享密钥和随机数进行加密,保护数据的安全。3.3应用示例Tendermint官方项目里内置了ABCIApplication的两个简单实现counter以及kvstore。这个两个Demo逻辑非常简单,运行起来也非常简单,以kvstore为例,只需要下面三条简单的指令就可以轻松的跑起来:tendermintinitabci-clikvstoretendermintnode复杂一点,假设想使用Tendermint实现一套类似Ethereum的应用,最终应该是这样:由TendermintCore负责交易和区块的共享以及共识处理,开发者只需将go-ethereum和ABCIServer集成一个ABCI应用。Ethermint项目就是Tendermint团队开发的一个类似应用,大家可以参考,遗憾的是目前Ethermint目前只支持低版本的abci和go-ethereum。4.Tendermint工作流程上图简单描述了Tenermint的工作流。大致为:(1)client通过RPC接口broadcast_tx_commit提交交易;(2)mempool调用ABCI接口CheckTx用于校验交易的有效性,比如交易序号、发送者余额等,同时订阅交易执行后的事件并等待监听。(3)共识从mempool中获取交易开始共识排序,打包区块,确定之后依次调用ABCI相关接口更新当前的事件状态,并触发事件。(4)最终将交易信息返回client。5.参考本文转载自《深度解析Tendermint,快速融入Cosmos生态》。更多Tendermint资料参考:(1)拜占庭共识Tendermint介绍及简单入门https://blog.csdn.net/niyuelin1990/article/details/80537329(2)Tendermint说明文档https://tendermint.readthedocs.io/en/master/(3)TendermintGIT地址https://github.com/tendermint/tendermint(4)深度解析Tendermint,快速融入Cosmos生态[质量高]https://zhuanlan.zhihu.com/p/38252058(5)区块链框架Tendermint入门教程https://hbliu.coding.me/2018/04/02/tendermint-introduction-1/(6)详解Tendermint共识算法https://www.odaily.com/post/5134145(7)分布式一致性协议介绍(Paxos、Raft)https://www.cnblogs.com/zhang-qc/p/8688258.html作者:笔名辉哥链接:https://www.jianshu.com/p/68fb29cd00de
区块链应用已经从单纯电子现金发展到去中心化投票等更多的领域,但是区块链这样的分布式系统的开发还存在一些困难的问题:安全、可靠性、敏捷度、以及一致性保证等等。Tendermint的目的就是致力于解决分布式系统开发中像公示算法这样的技术难点,而让Tendermint区块链应用开发者可以将关注点集中在业务逻辑上。如果希望快速掌握基于Tendermint的区块链开发,推荐汇智网的在线互动课程:Tendermint区块链开发详解,技术问题可以咨询课堂助教。Tendermint简介Tendermint萌芽于比特币、以太坊这样的加密货币,它的目标是提供一个比比特币的工作量证明(PoW)更加高效和安全的共识算法。简单地说,Tendermint是一个可供二次开发的软件包,可以在多台机器上安全、一致地实现应用状态的复制。Tendermint可以在不超过1/3的机器失效时依然正常工作,无论失效的原因是什么。Tendermint实现了拜占庭容错。任何正常工作的机器都会收到相同的交易日志,并分别推导出相同的状态Tendermint的特性如下图所示:Tendermint包含两个主要的组件:区块链共识引擎,即:Tendermint内核应用与区块链接口,即:ApplicationBlockChainInterfaceTendermint内核可以托管任意的应用状态,因此可以使用任何语言开发区块链软件:Haskell、GoLang、或者Rust都可以用来开发ABCI应用。其他区块链的一个问题是,它们都是单体设计思维的软件。以比特币为例,比特币的设计就是单体的,其区块链技术栈都包含在单一程序里,需要处理从P2P链接到交易广播、达成共识乃至检查账户余额的一切事情。单体应用通常不容易扩展、升级或再利用,而Tendermint则致力于将区块链技术栈的两个核心组件与其他部分解耦:共识引擎和P2P连接——事实上这也是开发区块链的最困难的两个技术环节——从而可以使用任何开发语言来开发ABCI应用。废话不多说了,让我们撸起袖子开干!Tendermint开发环境搭建与测试STEP1:下载Tendermint内核tendermint内核采用Go开发,有官方预编译程序,下载地址:TendermintCore。下载后直接解压,并将tendermint程序目录添加到环境变量PATH的设置里。STEP2:初始化Tendermint执行如下命令初始化Tendermint:~$tendermintinit应当可以在终端看到tendermint的输出信息:I[10–18|20:14:08.996]Generatedprivatevalidatormodule=mainpath=/Users/niharikasingh/.tendermint/config/priv_validator.jsonI[10–18|20:14:08.996]Generatednodekeymodule=mainpath=/Users/niharikasingh/.tendermint/config/node_key.jsonI[10–18|20:14:08.996]Generatedgenesisfilemodule=mainpath=/Users/niharikasingh/.tendermint/config/genesis.jsonSTEP3:启动Tendermint节点使用node子命令启动Tendermint节点:~$tendermintnode-proxy_app=kvstore-proxy_app运行标志用来指定一个内置的ABCI应用,例如kvstore是tendermint程序内置的键值对应用。你应该可以看到如下的tendermint程序输出:I[10–18|20:16:40.037]StartingmultiAppConnmodule=proxyimpl=multiAppConn...I[10–18|20:16:42.051]enterPropose:Ourturntoproposemodule=consensusheight=2round=0proposer=601302EBD1F8B4BCE9F99B219965F2796AB6BB10privValidator=”PrivValidator{601302EBD1F8B4BCE9F99B219965F2796AB6BB10LH:1,LR:0,LS:3}”I[10–18|20:16:42.055]Signedproposalmodule=consensusheight=2round=0proposal=”Proposal{2/01:48B45F4423A5(-1,:0:000000000000)F52DF1F111D8@2018–10–18T14:46:42.051967933Z}”I[10–18|20:16:42.056]Receivedproposalmodule=consensusproposal=”Proposal{2/01:48B45F4423A5(-1,STEP4:提交交易要提交一个交易,可以使用curl向Tendermint节点的RPC服务发出请求,例如:~$curlhttp://localhost:26657/broadcast_tx_commit?tx=\"niharika\"响应结果如下:{"jsonrpc":"2.0","id":"","result":{"check_tx":{"gasWanted":"1"},"deliver_tx":{"tags":[{"key":"YXBwLmNyZWF0b3I=","value":"amFl"},{"key":"YXBwLmtleQ==","value":"bmloYXJpa2E="}]},"hash":"EAAD936D3EDCCCF5DD214E02BB4065E5511CA5AC","height":"3533"}}注意结果中的value字段,例如bmloYXJpa2E,这其实是字符串niharika的base64编码。现在让我们查询一下:~$curl-s'localhost:26657/abci_query?data="niharika"'响应结果如下:{"jsonrpc":"2.0","id":"","result":{"response":{"log":"exists","index":"-1","key":"bmloYXJpa2E=","value":"bmloYXJpa2E="}}}很好,看起来我们的Tendermint内核与ABCI接口的工作一切正常!在本文中,我们成功安装并启动了tendermint内核,然后通过节点旳ABCI接口提交了一个交易来更新内置键值库应用的状态,最后通过ABCI接口查询了ABCI应用的状态。这就是基于Tendermint进行应用开发的核心模型:可以使用任何开发语言来代替curl完成这些操作,实现自己的ABCI应用!原文链接:Tendermint101-拥抱区块链的未来
Tendermint是什么?来自一段slack对话先来举个例子,Wordpress与ApacheWebServer,ApacheWebServer通过fastcgi与Wordpress进行交流。它们被组合到一个服务端的进程中,这个进程负责处理连接逻辑,比如控制流量和安全。Tendermint就像是分布式账本中的ApacheWebServer,它负责了像p2p网络,共识,交易广播等等之类的事情。对于应用任何商业性质的逻辑处理而言,Tendermint是透明的。而对Tendermint来说,这些逻辑处理也都只不过是二进制的字节而已。一旦网络中的验证人对一个块达成共识,并且想要提交这个块时,交易就会通过ABCI被推送到应用中,ABCI是一个网络套接字协议,它的作用就类似于在ApacheWebServer与Wordpress示例中的fastcgi.没有人知道谁会成为未来分布式账本中的Wordpress.另一种解释Tenermint是一个软件,用于在多台机器安全一致地复制一个应用。所谓安全,指的是即使有多达1/3的机器出现任意故障的情况下,Tendermint仍然能够正常工作。所谓一致,指的是每一个正常工作的机器都会有着同样的交易日志,计算相同的状态。安全一致的复制是分布式系统中一个至关重要的问题:从货币到选举,到基础设施规划,它在广泛应用的容错中承担了一个极其重要的角色。能够容忍机器以任何一种,甚至包括危害系统的方式发生故障,被称为拜占庭容错(BFT)。拜占庭理论已经有几十年的历史,但是很大程度上,直到最近像比特币,以太坊这样区块链技术的成功,它的软件实现才得以进一步发展。区块链技术只是以一种现代化的方式对BFT的再形式化,而且重点关注p2p网络和密码验证。区块链这个名词来源于交易的处理方式,通过区块的批量方式处理交易,每个块包含了前一个块的加密哈希,以此来形成一个链。实际上,区块链数据库真正地优化了BFT设计。Tendermint包含了两个主要的技术组件:一个区块链共识引擎和一个通用的应用程序接口。共识引擎,叫做TendermintCore,保证了每一台机器以相同的顺序记录同一笔交易。应用程序接口,叫做应用程序区块链接口(ABCI),保证了交易可以通过任何一种编程语言进行处理。与其他预先打包内置状态机(比如键值存储或者一个奇怪的脚本语言)的区块链和共识方案不同,开发者可以使用Tendermint实现应用的BFT状态机复制,而这些应用可以用任何语言编写,而且开发环境对开发者也十分友好。Tendermint的设计原则是易使用,易理解,高性能,对于各种分布式应用都十分有用。1.Tendermint与其他技术的比较大体上,Tendermint与两类软件很类似。第一类包含了分布式的键值存储,比如Zookeeper,etcd和consul,它们都使用了非拜占庭容错共识。第二类就是“区块链技术”,它既包括了像比特币和以太坊这样的加密货币,也包括了像HyperledgerBurrow这样的分布式账本设计。Zookeeper,etcd,consulZookeeper,etcd和consul都是在一个经典的非拜占庭容错共识算法上,实现了一个键值存储。Zookeeper使用了Paxos一个叫做ZookeeperAtomicBroadcast的版本,而etcd和consul则使用了更年轻,也更简单的Raft共识算法。一个典型的集群由3-5台机器构成,虽然可以承受1/2的机器发生问题,但是只要发生一次拜占庭故障,整个系统就可能被摧毁。它们每一个都提供了一个稍微有别于键值存储的实现,但是都将关注点放在提供分布式系统的基础服务上,比如动态配置,服务发现,锁定,领导人选取等等。Tendermint是一个本质上类似的软件,不过有两点关键不同:它是拜占庭容错的,这意味着它可以承受1/3机器发生任意形式的故障–包括黑客和恶意攻击。它并不像键值存储,是针对某一指定类型的应用。相反,它关注于任意的状态机复制,因此开发者可以量身打造适合自己的应用逻辑,从键值存储到加密货币到电子投票平台,甚至更多的应用都可适用。以上内容取自于consul.io和Hashicorpsites.Bitcoin,Ethereum,etc.在比特币和以太坊这样的传统加密货币下出现了Tendermint,它的目的在于提供一个比比特币的工作量证明更加有效和安全的共识算法。在早期,Tendermint内置了一个简单的货币来参与共识,用户必须向一个保证金账户中“绑定”一定数量的货币,如果他们表现不端,这些钱就会被收回–这一点使得Tendermint成为一个POS算法。自那时起,Tendermint就进化为一个能够承载任意应用状态的通用区块链共识引擎。这意味着它可以成为其他区块链软件共识引擎的一个即插即用的替代品。所以基于当前的以太坊代码库,无论是用何种语言,Rust,Go,Haskell,任何人都可以使用Tendermint共识运行一个ABCI应用。实际上,我们已经完成了这一点(ethermint)。此外,我们也计划为Bitcoin,ZCash,和其他确定性的应用完成同样的工作。另一个基于Tendermint构建的加密货币应用是Cosmos。Fabric,BurrowFabric采用了与Tendermint类似的方法,但是它更关注对状态的管理,并且要求所有的应用行为能够在多个docker容器,叫做“chaincode”的模块中运行。它使用了来自IBM(augmentedtohandlepotentiallynon-deterministicchaincode)的PBFT实现。通过扩展Tendermint来处理未来工作中存在的不确定性,在Tendermint中通过一个ABCI应用实现这个基于docker的行为是完全有可能的。Burrow是一个以太坊虚拟机和以太坊交易机制的实现,同时附带有名字注册,许可权和天然合约,可替代区块链API等额外特性。它使用Tendermint作为它的共识引擎,提供一个特殊的应用状态。2.什么是ABCI(应用区块链接口)区块链应用接口(ApplicationBlockChainInterface,ABCI)允许应用的拜占庭容错复制可以由任意一种编程语言编写。动机至今为止,所有的区块链“栈”(比如,比特币)都有着大一统的设计。这就是说,每个区块链栈都是一个单一的程序,这个程序处理了去中心化账本的所有事务。它还包括了P2P连接,交易的“内存池”广播,在最新块上的共识,账户余额,图灵完备的合约,用户级别的许可权等。在计算机科学中,使用大一统的架构,是一个典型的错误实践。这会使得代码重用变得困难,而且如果真的去这么做时,会导致代码库分支的维护变得十分复杂。尤其当代码设计并非模块化时,会产生难以维护的“意大利面条式代码”。大一统设计的另一个问题是,它限制了区块链栈的语言。比如在以太坊中,它支持一个图灵完备的字节码虚拟机,它限制你必须使用可以编译为那种类型字节码的语言。目前,它所支持的语言是Serpent和Solidity。相反,我们的方式是从特定区块链应用的应用状态细节中,将共识引擎和p2p层分离开来。我们通过将应用细节抽象为一个借口来实现这一点,这个接口被实现为一个socket协议。所以,我们就有了一个接口,应用区块链接口(ABCI),和它的主要实现,TendermintSocketProtocol(TSP,或Teaspoon)。ABCI介绍TendermintCore(“共识引擎”)通过一个满足ABCI标准的socket协议与应用进行交流。举个大家比较熟悉的例子,比特币。比特币是一个加密货币区块链,其中的每个节点维护了一个完全经过审计的UTXO数据库。如果有人想要在ABCI之上创建一个类似比特币的系统,TendermintCore将会负责:在节点间共享区块和交易建立交易(区块链)的标准/不可变顺序而应用将会负责:维护UTXO数据库验证交易的加密签名阻止花费尚未存在的交易允许客户端查询UTXO数据库Tendermint能够通过在应用过程和共识过程之间,提供一个非常简单的API(也就是ABCI)来分解区块链设计。ABCI包含了3个主要的消息类型,它们由core发送至应用,应用会对消息产生相应的回复。消息的详细说明在这里:ABCI消息类型。DeliverTx消息是应用的主要部分。链中的每笔交易都通过这个消息进行传送。应用需要基于当前状态,应用协议,和交易的加密证书上,去验证接收到DeliverTx消息的每笔交易,。一个经过验证的交易然后需要去更新应用状态–比如通过将绑定一个值到键值存储,或者通过更新UTXO数据库。CheckTx消息类似于DeliverTx,但是它仅用于验证交易。TendermintCore的内存池首先通过CheckTx检验一笔交易的有效性,并且只将有效交易中继到其他节点。比如,一个应用可能会检查在交易中不断增长的序列号,如果序列号过时,CheckTx就会返回一个错误。又或者,他们可能使用一个基于容量的系统,该系统需要对每笔交易重新更新容量。Commit消息用于计算当前应用状态的一个加密保证(cryptographiccommitment),这个加密保证会被放到下一个区块头。这有一些比较方便的属性。现在,更新状态时的不一致性会被认为是区块链的分支,分支会捕获所有的编程错误。这同样也简化了保障轻节点客户端安全的开发,因为Merkel-hash证明可以通过在区块哈希上的检查得到验证,区块链哈希由一个quorum签署。一个应用可能有多个ABCIsocket连接。TendermintCore给应用创建了三个ABCI连接:一个用于内存池广播时的交易验证,一个用于运行提交区块时的共识引擎,还有一个用于查询应用状态。很显然,在创建区块链时,应用的设计者需要非常小心地设计他们的消息处理,这个架构提供一个范例。下图阐释了通过ABCI的消息流:关于确定性的说明区块链交易处理的逻辑必须是确定性的。如果应用逻辑不确定,就无法在TendermintCore复制节点间达成共识。在以太坊上的Solidity是用于区块链应用一个非常好的语言选择,除了一些其他因素,它还是一个完全确定性的编程语言。但是,通过使用现有的一些语言,比如Java,C++,Python和Go也是可以创建确定性应用的。对通过避免非确定性来源创建确定性程序,游戏程序员和区块链开发者都已经很熟悉了,比如:随机数生成器(没有确定性的种子)线程上的竞争条件(或者避免多线程)系统时钟未初始化的内存(在像C或者C++这样的不安全语言)浮点数算法随机的语言特性(比如Go语言的map迭代)尽管程序员可以通过加倍小心来避免非确定性,但是给每个语言创建一个特殊的语法检查器或静态分析器,用它们来检查确定性也是有可能的。在未来,我们可能会与合作者一起创造出这样的工具。3.共识概览Tendermint是一个易于理解,大部分操作为异步的BFT共识协议。下图是一个简单的状态机,它展示了协议遵循的规则:协议中的参与者叫着“验证人”(validator)。他们轮流对交易区块进行提议,并对这些区块进行投票。区块会被提交到链上,每一个块占据一个“高度”(height)。提交块可能会失败,如果失败,协议就会开始下一轮的提交,并且一个新的验证人会继续提交那个高度的区块。要想成功提交一个块,需要有两个阶段的投票:“预投票”(pre-vote)和“预提交”(pre-commit)。在同一轮提交中,只有超过2/3的验证人对同一个块进行了预提交,这个块才能被提交到链上。上图右下角有一对夫妇在跳波卡舞,因为验证人做的事情就像是在跳波卡舞。当超过2/3的验证人对同一个块进行了预投票,我们就把它叫做一个“波卡”(polka)。每一个预提交都必须被同一轮中的一个波卡所证明。由于一些原因,验证人可能在提交一个块时失败:当前提议者可能离线了,或者网络非常慢。Tendermint允许他们证实一个验证人应该被跳过。在进行下一轮的投票前,验证人会等待一小段时间从提议者那里接收一个完整的提议块。这种对于超时的依赖,使得Tendermint成为了一个弱同步协议,而非一个异步协议。但是,协议的剩余部分都是异步的,只有在接收到超过2/3的验证人集合时,验证人才会采取下一步操作。Tendermint能够简化的一个原因就是它使用了同样的机制来提交一个块和跳过直接进入下一轮。基于不到1/3的验证人是拜占庭节点的前提,Tendermint保证了永远都不会违背其安全性–也就是说,验证人永远不会在同一高度提交冲突块。为了达到这一点,它引入了一些“锁定”(locking)的规则,这些规则对流程图中的路径进行了模块化。一旦一个验证人预提交了一个块,它就被“锁定”在了那个块上。然后,它必须为被锁定的那个块进行预投票只有在之后的轮中,有了那个块的一个波卡,它才能够解锁,并为一个新块进行预提交。权益在许多系统中,并非所有的验证人都在共识协议有着同样的“高度”(height)。因此,我们对1/3还是2/3的验证人并不十分感兴趣,而是关心在所有投票力量所占的比例,在个体验证人中,这些比例可能并不是均匀分布的。由于Tendermint可以复制任意的应用程序,定义一种货币,并用该货币来计算投票权力是完全有可能的。当使用货币决定投票权时,这个系统通常叫做权益证明(Proof-of-Stake)系统。通过应用逻辑,可以将验证人的货币持有强制绑定到一个押金账户中。如果他们被发现在共识协议中表现不端,这些钱就会被销毁。这就给协议的安全性增加了一个经济因素,能够让人们量化违反共识假设的成本,这个假设就是只有不到1/3的投票权来自拜占庭节点。CosmosNetwork的设计目的,是在实现了ABCI应用的加密货币中使用这个权益证明机制。原文:Tendermintintro
开篇说几句,网上让用homebrew安装nodejs的都是坑人的。我踩坑无数。特此记录。只需以下几行代码sudoapt-getupdate#安装系统依赖sudoapt-getinstallbuild-essentialgitopenssl#安装nodescurl-sLhttps://deb.nodesource.com/setup_5.x|sudo-Ebash-sudoapt-getinstallnodejs#修复npm全局安装的文件权限问题mkdir~/.npm-globalnpmconfigsetprefix'~/.npm-global'#设置Bash环境变量echo[[-s"$HOME/.profile"]]&&source$HOME/.profile'>>~/.bash_profileecho'exportPATH=~/.npm-global/bin:$PATH'>>~/.profilesource~/.profile#安装testrpcnpminstall-gethereumjs-testrpc#安装trufflenpminstall-gtruffle
傅里叶分析之掐死教程(完整版)原文出处:韩昊12345678910作者:韩昊知乎:Heinrich微博:@花生油工人知乎专栏:与时间无关的故事谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。——更新于2014.6.6,想直接看更新的同学可以直接跳到第四章————我保证这篇文章和你以前看过的所有文章都不同,这是2012年还在果壳的时候写的,但是当时没有来得及写完就出国了……于是拖了两年,嗯,我是拖延症患者……这篇文章的核心思想就是:要让读者在不看任何数学公式的情况下理解傅里叶分析。傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是,傅里叶分析的公式看起来太复杂了,所以很多大一新生上来就懵圈并从此对它深恶痛绝。老实说,这么有意思的东西居然成了大学里的杀手课程,不得不归咎于编教材的人实在是太严肃了。(您把教材写得好玩一点会死吗?会死吗?)所以我一直想写一个有意思的文章来解释傅里叶分析,有可能的话高中生都能看懂的那种。所以,不管读到这里的您从事何种工作,我保证您都能看懂,并且一定将体会到通过傅里叶分析看到世界另一个样子时的快感。至于对于已经有一定基础的朋友,也希望不要看到会的地方就急忙往后翻,仔细读一定会有新的发现。————以上是定场诗————下面进入正题:抱歉,还是要啰嗦一句:其实学习本来就不是易事,我写这篇文章的初衷也是希望大家学习起来更加轻松,充满乐趣。但是千万!千万不要把这篇文章收藏起来,或是存下地址,心里想着:以后有时间再看。这样的例子太多了,也许几年后你都没有再打开这个页面。无论如何,耐下心,读下去。这篇文章要比读课本要轻松、开心得多……一、什么是频域从我们出生,我们看到的世界都以时间贯穿,股票的走势、人的身高、汽车的轨迹都会随着时间发生改变。这种以时间作为参照来观察动态世界的方法我们称其为时域分析。而我们也想当然的认为,世间万物都在随着时间不停的改变,并且永远不会静止下来。但如果我告诉你,用另一种方法来观察世界的话,你会发现世界是永恒不变的,你会不会觉得我疯了?我没有疯,这个静止的世界就叫做频域。先举一个公式上并非很恰当,但意义上再贴切不过的例子:在你的理解中,一段音乐是什么呢?这是我们对音乐最普遍的理解,一个随着时间变化的震动。但我相信对于乐器小能手们来说,音乐更直观的理解是这样的:好的!下课,同学们再见。是的,其实这一段写到这里已经可以结束了。上图是音乐在时域的样子,而下图则是音乐在频域的样子。所以频域这一概念对大家都从不陌生,只是从来没意识到而已。现在我们可以回过头来重新看看一开始那句痴人说梦般的话:世界是永恒的。将以上两图简化:时域:频域:在时域,我们观察到钢琴的琴弦一会上一会下的摆动,就如同一支股票的走势;而在频域,只有那一个永恒的音符。所以你眼中看似落叶纷飞变化无常的世界,实际只是躺在上帝怀中一份早已谱好的乐章。抱歉,这不是一句鸡汤文,而是黑板上确凿的公式:傅里叶同学告诉我们,任何周期函数,都可以看作是不同振幅,不同相位正弦波的叠加。在第一个例子里我们可以理解为,利用对不同琴键不同力度,不同时间点的敲击,可以组合出任何一首乐曲。而贯穿时域与频域的方法之一,就是传中说的傅里叶分析。傅里叶分析可分为傅里叶级数(FourierSerie)和傅里叶变换(FourierTransformation),我们从简单的开始谈起。二、傅里叶级数(FourierSeries)的频谱还是举个栗子并且有图有真相才好理解。如果我说我能用前面说的正弦曲线波叠加出一个带90度角的矩形波来,你会相信吗?你不会,就像当年的我一样。但是看看下图:第一幅图是一个郁闷的正弦波cos(x)第二幅图是2个卖萌的正弦波的叠加cos(x)+a.cos(3x)第三幅图是4个发春的正弦波的叠加第四幅图是10个便秘的正弦波的叠加随着正弦波数量逐渐的增长,他们最终会叠加成一个标准的矩形,大家从中体会到了什么道理?(只要努力,弯的都能掰直!)随着叠加的递增,所有正弦波中上升的部分逐渐让原本缓慢增加的曲线不断变陡,而所有正弦波中下降的部分又抵消了上升到最高处时继续上升的部分使其变为水平线。一个矩形就这么叠加而成了。但是要多少个正弦波叠加起来才能形成一个标准90度角的矩形波呢?不幸的告诉大家,答案是无穷多个。(上帝:我能让你们猜着我?)不仅仅是矩形,你能想到的任何波形都是可以如此方法用正弦波叠加起来的。这是没有接触过傅里叶分析的人在直觉上的第一个难点,但是一旦接受了这样的设定,游戏就开始有意思起来了。还是上图的正弦波累加成矩形波,我们换一个角度来看看:在这几幅图中,最前面黑色的线就是所有正弦波叠加而成的总和,也就是越来越接近矩形波的那个图形。而后面依不同颜色排列而成的正弦波就是组合为矩形波的各个分量。这些正弦波按照频率从低到高从前向后排列开来,而每一个波的振幅都是不同的。一定有细心的读者发现了,每两个正弦波之间都还有一条直线,那并不是分割线,而是振幅为0的正弦波!也就是说,为了组成特殊的曲线,有些正弦波成分是不需要的。这里,不同频率的正弦波我们成为频率分量。好了,关键的地方来了!!如果我们把第一个频率最低的频率分量看作“1”,我们就有了构建频域的最基本单元。对于我们最常见的有理数轴,数字“1”就是有理数轴的基本单元。(好吧,数学称法为——基。在那个年代,这个字还没有其他奇怪的解释,后面还有正交基这样的词汇我会说吗?)时域的基本单元就是“1秒”,如果我们将一个角频率为的正弦波cos(t)看作基础,那么频域的基本单元就是。有了“1”,还要有“0”才能构成世界,那么频域的“0”是什么呢?cos(0t)就是一个周期无限长的正弦波,也就是一条直线!所以在频域,0频率也被称为直流分量,在傅里叶级数的叠加中,它仅仅影响全部波形相对于数轴整体向上或是向下而不改变波的形状。接下来,让我们回到初中,回忆一下已经死去的八戒,啊不,已经死去的老师是怎么定义正弦波的吧。正弦波就是一个圆周运动在一条直线上的投影。所以频域的基本单元也可以理解为一个始终在旋转的圆想看动图的同学请戳这里:File:Fourierseriessquarewavecirclesanimation.gif以及这里:File:Fourierseriessawtoothwavecirclesanimation.gif点出去的朋友不要被wiki拐跑了,wiki写的哪有这里的文章这么没节操是不是。介绍完了频域的基本组成单元,我们就可以看一看一个矩形波,在频域里的另一个模样了:这是什么奇怪的东西?这就是矩形波在频域的样子,是不是完全认不出来了?教科书一般就给到这里然后留给了读者无穷的遐想,以及无穷的吐槽,其实教科书只要补一张图就足够了:频域图像,也就是俗称的频谱,就是——再清楚一点:可以发现,在频谱中,偶数项的振幅都是0,也就对应了图中的彩色直线。振幅为0的正弦波。动图请戳:File:Fourierseriesandtransform.gif老实说,在我学傅里叶变换时,维基的这个图还没有出现,那时我就想到了这种表达方法,而且,后面还会加入维基没有表示出来的另一个谱——相位谱。但是在讲相位谱之前,我们先回顾一下刚刚的这个例子究竟意味着什么。记得前面说过的那句“世界是静止的”吗?估计好多人对这句话都已经吐槽半天了。想象一下,世界上每一个看似混乱的表象,实际都是一条时间轴上不规则的曲线,但实际这些曲线都是由这些无穷无尽的正弦波组成。我们看似不规律的事情反而是规律的正弦波在时域上的投影,而正弦波又是一个旋转的圆在直线上的投影。那么你的脑海中会产生一个什么画面呢?我们眼中的世界就像皮影戏的大幕布,幕布的后面有无数的齿轮,大齿轮带动小齿轮,小齿轮再带动更小的。在最外面的小齿轮上有一个小人——那就是我们自己。我们只看到这个小人毫无规律的在幕布前表演,却无法预测他下一步会去哪。而幕布后面的齿轮却永远一直那样不停的旋转,永不停歇。这样说来有些宿命论的感觉。说实话,这种对人生的描绘是我一个朋友在我们都是高中生的时候感叹的,当时想想似懂非懂,直到有一天我学到了傅里叶级数……三、傅里叶级数(FourierSeries)的相位谱上一章的关键词是:从侧面看。这一章的关键词是:从下面看。在这一章最开始,我想先回答很多人的一个问题:傅里叶分析究竟是干什么用的?这段相对比较枯燥,已经知道了的同学可以直接跳到下一个分割线。先说一个最直接的用途。无论听广播还是看电视,我们一定对一个词不陌生——频道。频道频道,就是频率的通道,不同的频道就是将不同的频率作为一个通道来进行信息传输。下面大家尝试一件事:先在纸上画一个sin(x),不一定标准,意思差不多就行。不是很难吧。好,接下去画一个sin(3x)+sin(5x)的图形。别说标准不标准了,曲线什么时候上升什么时候下降你都不一定画的对吧?好,画不出来不要紧,我把sin(3x)+sin(5x)的曲线给你,但是前提是你不知道这个曲线的方程式,现在需要你把sin(5x)给我从图里拿出去,看看剩下的是什么。这基本是不可能做到的。但是在频域呢?则简单的很,无非就是几条竖线而已。所以很多在时域看似不可能做到的数学操作,在频域相反很容易。这就是需要傅里叶变换的地方。尤其是从某条曲线中去除一些特定的频率成分,这在工程上称为滤波,是信号处理最重要的概念之一,只有在频域才能轻松的做到。再说一个更重要,但是稍微复杂一点的用途——求解微分方程。(这段有点难度,看不懂的可以直接跳过这段)微分方程的重要性不用我过多介绍了。各行各业都用的到。但是求解微分方程却是一件相当麻烦的事情。因为除了要计算加减乘除,还要计算微分积分。而傅里叶变换则可以让微分和积分在频域中变为乘法和除法,大学数学瞬间变小学算术有没有。傅里叶分析当然还有其他更重要的用途,我们随着讲随着提。————————————————————————————————————下面我们继续说相位谱:通过时域到频域的变换,我们得到了一个从侧面看的频谱,但是这个频谱并没有包含时域中全部的信息。因为频谱只代表每一个对应的正弦波的振幅是多少,而没有提到相位。基础的正弦波A.sin(wt+θ)中,振幅,频率,相位缺一不可,不同相位决定了波的位置,所以对于频域分析,仅仅有频谱(振幅谱)是不够的,我们还需要一个相位谱。那么这个相位谱在哪呢?我们看下图,这次为了避免图片太混论,我们用7个波叠加的图。鉴于正弦波是周期的,我们需要设定一个用来标记正弦波位置的东西。在图中就是那些小红点。小红点是距离频率轴最近的波峰,而这个波峰所处的位置离频率轴有多远呢?为了看的更清楚,我们将红色的点投影到下平面,投影点我们用粉色点来表示。当然,这些粉色的点只标注了波峰距离频率轴的距离,并不是相位。这里需要纠正一个概念:时间差并不是相位差。如果将全部周期看作2Pi或者360度的话,相位差则是时间差在一个周期中所占的比例。我们将时间差除周期再乘2Pi,就得到了相位差。在完整的立体图中,我们将投影得到的时间差依次除以所在频率的周期,就得到了最下面的相位谱。所以,频谱是从侧面看,相位谱是从下面看。下次偷看女生裙底被发现的话,可以告诉她:“对不起,我只是想看看你的相位谱。”注意到,相位谱中的相位除了0,就是Pi。因为cos(t+Pi)=-cos(t),所以实际上相位为Pi的波只是上下翻转了而已。对于周期方波的傅里叶级数,这样的相位谱已经是很简单的了。另外值得注意的是,由于cos(t+2Pi)=cos(t),所以相位差是周期的,pi和3pi,5pi,7pi都是相同的相位。人为定义相位谱的值域为(-pi,pi],所以图中的相位差均为Pi。最后来一张大集合:四、傅里叶变换(FourierTranformation)相信通过前面三章,大家对频域以及傅里叶级数都有了一个全新的认识。但是文章在一开始关于钢琴琴谱的例子我曾说过,这个栗子是一个公式错误,但是概念典型的例子。所谓的公式错误在哪里呢?傅里叶级数的本质是将一个周期的信号分解成无限多分开的(离散的)正弦波,但是宇宙似乎并不是周期的。曾经在学数字信号处理的时候写过一首打油诗:往昔连续非周期,回忆周期不连续,任你ZT、DFT,还原不回去。(请无视我渣一样的文学水平……)在这个世界上,有的事情一期一会,永不再来,并且时间始终不曾停息地将那些刻骨铭心的往昔连续的标记在时间点上。但是这些事情往往又成为了我们格外宝贵的回忆,在我们大脑里隔一段时间就会周期性的蹦出来一下,可惜这些回忆都是零散的片段,往往只有最幸福的回忆,而平淡的回忆则逐渐被我们忘却。因为,往昔是一个连续的非周期信号,而回忆是一个周期离散信号。是否有一种数学工具将连续非周期信号变换为周期离散信号呢?抱歉,真没有。比如傅里叶级数,在时域是一个周期且连续的函数,而在频域是一个非周期离散的函数。这句话比较绕嘴,实在看着费事可以干脆回忆第一章的图片。而在我们接下去要讲的傅里叶变换,则是将一个时域非周期的连续信号,转换为一个在频域非周期的连续信号。算了,还是上一张图方便大家理解吧:或者我们也可以换一个角度理解:傅里叶变换实际上是对一个周期无限大的函数进行傅里叶变换。所以说,钢琴谱其实并非一个连续的频谱,而是很多在时间上离散的频率,但是这样的一个贴切的比喻真的是很难找出第二个来了。因此在傅里叶变换在频域上就从离散谱变成了连续谱。那么连续谱是什么样子呢?你见过大海么?为了方便大家对比,我们这次从另一个角度来看频谱,还是傅里叶级数中用到最多的那幅图,我们从频率较高的方向看。以上是离散谱,那么连续谱是什么样子呢?尽情的发挥你的想象,想象这些离散的正弦波离得越来越近,逐渐变得连续……直到变得像波涛起伏的大海:很抱歉,为了能让这些波浪更清晰的看到,我没有选用正确的计算参数,而是选择了一些让图片更美观的参数,不然这图看起来就像屎一样了。不过通过这样两幅图去比较,大家应该可以理解如何从离散谱变成了连续谱的了吧?原来离散谱的叠加,变成了连续谱的累积。所以在计算上也从求和符号变成了积分符号。不过,这个故事还没有讲完,接下去,我保证让你看到一幅比上图更美丽壮观的图片,但是这里需要介绍到一个数学工具才能然故事继续,这个工具就是——五、宇宙耍帅第一公式:欧拉公式虚数i这个概念大家在高中就接触过,但那时我们只知道它是-1的平方根,可是它真正的意义是什么呢?这里有一条数轴,在数轴上有一个红色的线段,它的长度是1。当它乘以3的时候,它的长度发生了变化,变成了蓝色的线段,而当它乘以-1的时候,就变成了绿色的线段,或者说线段在数轴上围绕原点旋转了180度。我们知道乘-1其实就是乘了两次i使线段旋转了180度,那么乘一次i呢——答案很简单——旋转了90度。同时,我们获得了一个垂直的虚数轴。实数轴与虚数轴共同构成了一个复数的平面,也称复平面。这样我们就了解到,乘虚数i的一个功能——旋转。现在,就有请宇宙第一耍帅公式欧拉公式隆重登场——这个公式在数学领域的意义要远大于傅里叶分析,但是乘它为宇宙第一耍帅公式是因为它的特殊形式——当x等于Pi的时候。经常有理工科的学生为了跟妹子表现自己的学术功底,用这个公式来给妹子解释数学之美:”石榴姐你看,这个公式里既有自然底数e,自然数1和0,虚数i还有圆周率pi,它是这么简洁,这么美丽啊!“但是姑娘们心里往往只有一句话:”臭屌丝……“这个公式关键的作用,是将正弦波统一成了简单的指数形式。我们来看看图像上的涵义:欧拉公式所描绘的,是一个随着时间变化,在复平面上做圆周运动的点,随着时间的改变,在时间轴上就成了一条螺旋线。如果只看它的实数部分,也就是螺旋线在左侧的投影,就是一个最基础的余弦函数。而右侧的投影则是一个正弦函数。关于复数更深的理解,大家可以参考:复数的物理意义是什么?这里不需要讲的太复杂,足够让大家理解后面的内容就可以了。六、指数形式的傅里叶变换有了欧拉公式的帮助,我们便知道:正弦波的叠加,也可以理解为螺旋线的叠加在实数空间的投影。而螺旋线的叠加如果用一个形象的栗子来理解是什么呢?光波高中时我们就学过,自然光是由不同颜色的光叠加而成的,而最著名的实验就是牛顿师傅的三棱镜实验:所以其实我们在很早就接触到了光的频谱,只是并没有了解频谱更重要的意义。但不同的是,傅里叶变换出来的频谱不仅仅是可见光这样频率范围有限的叠加,而是频率从0到无穷所有频率的组合。这里,我们可以用两种方法来理解正弦波:第一种前面已经讲过了,就是螺旋线在实轴的投影。另一种需要借助欧拉公式的另一种形式去理解:将以上两式相加再除2,得到:这个式子可以怎么理解呢?我们刚才讲过,e^(it)可以理解为一条逆时针旋转的螺旋线,那么e^(-it)则可以理解为一条顺时针旋转的螺旋线。而cos(t)则是这两条旋转方向不同的螺旋线叠加的一半,因为这两条螺旋线的虚数部分相互抵消掉了!举个例子的话,就是极化方向不同的两束光波,磁场抵消,电场加倍。这里,逆时针旋转的我们称为正频率,而顺时针旋转的我们称为负频率(注意不是复频率)。好了,刚才我们已经看到了大海——连续的傅里叶变换频谱,现在想一想,连续的螺旋线会是什么样子:想象一下再往下翻:是不是很漂亮?你猜猜,这个图形在时域是什么样子?哈哈,是不是觉得被狠狠扇了一个耳光。数学就是这么一个把简单的问题搞得很复杂的东西。顺便说一句,那个像大海螺一样的图,为了方便观看,我仅仅展示了其中正频率的部分,负频率的部分没有显示出来。如果你认真去看,海螺图上的每一条螺旋线都是可以清楚的看到的,每一条螺旋线都有着不同的振幅(旋转半径),频率(旋转周期)以及相位。而将所有螺旋线连成平面,就是这幅海螺图了。好了,讲到这里,相信大家对傅里叶变换以及傅里叶级数都有了一个形象的理解了,我们最后用一张图来总结一下:好了,傅里叶的故事终于讲完了,下面来讲讲我的故事:这篇文章第一次被卸下来的地方你们绝对猜不到在哪,是在一张高数考试的卷子上。当时为了刷分,我重修了高数(上),但是后来时间紧压根没复习,所以我就抱着裸考的心态去了考场。但是到了考场我突然意识到,无论如何我都不会比上次考的更好了,所以干脆写一些自己对于数学的想法吧。于是用了一个小时左右的时间在试卷上洋洋洒洒写了本文的第一草稿。你们猜我的了多少分?6分没错,就是这个数字。而这6分的成绩是因为最后我实在无聊,把选择题全部填上了C,应该是中了两道,得到了这宝贵的6分。说真的,我很希望那张卷子还在,但是应该不太可能了。那么你们猜猜我第一次信号与系统考了多少分呢?45分没错,刚刚够参加补考的。但是我心一横没去考,决定重修。因为那个学期在忙其他事情,学习真的就抛在脑后了。但是我知道这是一门很重要的课,无论如何我要吃透它。说真的,信号与系统这门课几乎是大部分工科课程的基础,尤其是通信专业。在重修的过程中,我仔细分析了每一个公式,试图给这个公式以一个直观的理解。虽然我知道对于研究数学的人来说,这样的学习方法完全没有前途可言,因为随着概念愈加抽象,维度越来越高,这种图像或者模型理解法将完全丧失作用。但是对于一个工科生来说,足够了。后来来了德国,这边学校要求我重修信号与系统时,我彻底无语了。但是没办法,德国人有时对中国人就是有种藐视,觉得你的教育不靠谱。所以没办法,再来一遍吧。这次,我考了满分,而及格率只有一半。老实说,数学工具对于工科生和对于理科生来说,意义是完全不同的。工科生只要理解了,会用,会查,就足够了。但是很多高校却将这些重要的数学课程教给数学系的老师去教。这样就出现一个问题,数学老师讲得天花乱坠,又是推理又是证明,但是学生心里就只有一句话:学这货到底干嘛用的?缺少了目标的教育是彻底的失败。在开始学习一门数学工具的时候,学生完全不知道这个工具的作用,现实涵义。而教材上有只有晦涩难懂,定语就二十几个字的概念以及看了就眼晕的公式。能学出兴趣来就怪了!好在我很幸运,遇到了大连海事大学的吴楠老师。他的课全程来看是两条线索,一条从上而下,一条从下而上。先将本门课程的意义,然后指出这门课程中会遇到哪样的问题,让学生知道自己学习的某种知识在现实中扮演的角色。然后再从基础讲起,梳理知识树,直到延伸到另一条线索中提出的问题,完美的衔接在一起!这样的教学模式,我想才是大学里应该出现的。最后,写给所有给我点赞并留言的同学。真的谢谢大家的支持,也很抱歉不能一一回复。因为知乎专栏的留言要逐次加载,为了看到最后一条要点很多次加载。当然我都坚持看完了,只是没办法一一回复。本文只是介绍了一种对傅里叶分析新颖的理解方法,对于求学,还是要踏踏实实弄清楚公式和概念,学习,真的没有捷径。但至少通过本文,我希望可以让这条漫长的路变得有意思一些。最后,祝大家都能在学习中找到乐趣…