区块链是当下最热门的话题。大多数读者都听说过比特币,也许还有智能合约。相信大家都想知道这是怎么回事。怎么运行的。本文就是帮助大家用Java语言实现一个简单的区块链,用不到120行的代码揭示区块链的原理!“你可以用不到120行Java代码实现你自己的区块链!”听起来不可思议,对吧?有什么比开发自己的区块链更好的学习和实践方式呢?那我们一起练习吧!因为我们是一家从事互联网金融的科技公司,所以本文以虚拟资产的数量作为样本数据。您可以先自己想一个数字,我们稍后会用到它。通过本文,您将能够:1.创建自己的区块链2.了解哈希函数如何维护区块链的完整性3.如何创建和添加新区块4.多个节点如何竞争生成区块5.查看通过浏览器浏览整条链6.其他所有关于区块链的基础知识但是,工作量证明算法(PoW)和股权证明算法(PoS)等共识算法将不在本文中介绍。同时,为了让您更清楚地查看区块链和区块的添加,我们简化了网络交互的过程。“点对点网络”等P2P网络的内容将在以后的文章中进行讲解。开始吧!设置我们假设你已经有一点Java语言开发经验和maven项目构建经验。安装配置好Java开发环境后,我们新建一个maven工程,在pom中添加一些依赖:com.sparkjavaspark-core${spark.version}Spark-webFramework是一个基于jetty的超小型框架,我们用它来写http访问请求处理。commons-codeccommons-codec${commons.codec.version}这个通用包几乎所有的加密和解密算法及套路操作com.google.code.gsongson2.8.2当然是谷歌的json包你可以使用你喜欢的任何其他json包。***、添加日志相关包log4jlog4j${log4j.version}org.slf4jslf4j-api<版本>${slf4j.version}org.slf4jslf4j-log4j12${slf4j.version}相关版本属性设置1.92.6.01.6.61.2.172.8.2接下来,我们创建一个SparkWeb.java文件。之后我们的大部分工作都围绕这个文件展开,所以让我开始编码吧!数据模型我们定义一个Block类,代表组成区块链的每一个区块的数据模型:publicclassBlock{/**是这个区块在整条链中的位置*/privateintindex;/**很明显是当区块生成时间戳*/privateStringtimestamp;/**虚拟资产。我们要记录的数据*/privateintvac;/**是本区块通过SHA256算法生成的哈希值*/privateStringhash;/**指向上一个区块的SHA256哈希值*/privateStringprevHash;/**gettersandsetters**/}接下来,我们定义一个结构来表示整个链。最简单的表示形式是区块序列表:ArrayListblockChain我们使用哈希算法(SHA256)来确定和维护链中区块和区块的正确顺序,确保每个区块的PrevHash值相等到前一个块中的哈希值,以便以正确的块顺序构建链:[index:0|散列:“xxxw”|预哈希:“”]-[索引:1|散列:“xxxx”|preHash:"xxxw"]-[index2|散列:“xxxy”|preHash:"xxxx"]哈希和生成块为什么我们需要哈希?主要有两个原因:1、在节省空间的前提下,数据唯一标识。哈希是使用整个块的数据计算的。在我们的例子中,整个区块的数据通过SHA256计算成一个定长的不可伪造的字符串。2.维护链条的完整性。通过存储前一个块的哈希,我们能够确保链中每个块的正确顺序。任何对数据的篡改都会改变散列并破坏链条。以我们所从事的医疗健康领域为例。例如,如果恶意第三方为了调整“人寿保险”的价格,在一个或几个区块中修改不健康的VAC值,那么整个链条将变得不可信任。向上。然后我们编写一个函数来计算给定数据的SHA256哈希值:publicstaticStringcalculateHash(Blockblock){Stringrecord=(block.getIndex())+block.getTimestamp()+(block.getVac())+block。getPrevHash();returnSHA256.crypt(record);}接下来我们可以得到一个出块函数:publicstaticBlockgenerateBlock(BlockoldBlock,intvac){BlocknewnewBlock=newBlock();newBlock.setIndex(oldBlock.getIndex()+1);newBlock.setTimestamp(newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(newDate()));newBlock.setVac(vac);newBlock.setPrevHash(oldBlock.getHash());newBlock.setHash(calculateHash(newBlock));returnnewBlock;}其中Index是从给定的前一个block的Index递增,时间戳是直接通过newDate()函数得到的,Hash值是通过前一个calculateHash函数计算的,PrevHash它是给定的前一个块的哈希值。验证块已经完成了块的生成,接下来我们需要一个函数来帮助我们判断一个块是否被篡改。检查Index看区块是否正确递增,检查PrevHash是否与上一个区块的Hash一致,再通过calculateHash检查当前区块的Hash值是否正确。通过这几步我们可以写出一个验证函数:newBlock.getPrevHash())){returnfalse;}if(!calculateHash(newBlock).equals(newBlock.getHash())){returnfalse;}returntrue;}除了checkblock,我们还会遇到一个Question:两个节点都生成块并添加到各自的链中,那么我们应该判断谁呢?细节留到下一篇,在这里记住一个原则:永远选择最长的链。[块1]->[块2]->[块3]->[块4]->[块5]->批准[块1]->[块2]->[块3]->[block4]->Discard通常来说,链越长意味着它的数据(状态)越新,所以我们需要一个函数可以帮助我们将本地过期的链切换到最新的链:publicvoidreplaceChain(ArrayListnewBlocks){if(newBlocks.size()>blockChain.size()){blockChain=newBlocks;}}至此,我们基本完成了所有重要功能。接下来,我们需要一种方便直观的方式来查看我们的链,包括数据和状态。通过浏览器查看网页可能是最合适的方式。Webservices想必大家对传统的webservices和开发都非常熟悉,所以这部分肯定一眼就知道了。使用SparkWebFramework来完成我们的web服务,代码如下:publicstaticvoidmain(String[]args){//port(5678);//默认端口为4567,可以设置其他端口}OK,大功告成,是的,你没有看错,就是一个空的main方法,仅此而已。接下来我们定义不同的端点和相应的处理程序。例如,对“/”的GET请求允许我们查看整个链,对“/”的POST请求创建一个新块。GET请求处理程序:get("/",(q,a)->{returngson.toJson(blockChain)});为了简单起见,我们直接以JSON格式返回整个链,你可以在浏览器中访问localhost:4567或者127.0.0.1:4567来查看POST请求的handler有点复杂,我们先定义POST请求的payload:publicclassMessage{privateintvac;//gettersandsetters}然后看handler的实现:post("/",(q,a)->{Stringbody=request.body();Messagem=gson.fromJson(body,Message.class);if(m==null){return"vacisNULL";}intvac=m.getVac();BlocklastBlock=blockChain.get(blockChain.size()-1);BlocknewBlock=generateBlock(lastBlock,vac);if(isBlockValid(newBlock,lastBlock)){blockChain.add(newBlock);LOGGER.debug(gson.toJson(blockChain));}else{return"HTTP500:InvalidBlockError";}return"success!";});我们可以在我们的POST请求体中使用上面定义的payload,例如:{"vac":7500}还记得我们之前写的generateBlock这个函数吗?它需要一个“前一个块”参数和一个VAC值。POST处理程序接受请求后,可以获取请求体中的VAC值,然后借助出块函数和验证块函数生成新的块!除此之外,还可以:1、使用新增的GsonBuilder().setPrettyPrinting().create()函数,将json中的数据打印在控制台中,非常漂亮易读,方便调试.2、测试POST请求时,可以使用chrome插件POSTMAN,比curl更直观方便。您还可以使用RESTClientFireFox插件。差不多大功告成接下来,我们“组装”这些关于区块链的功能和web服务的功能:最重要的是我们需要生成第一个区块(genesisblock)作为区块链的头部。//创世块BlockgenesisBlock=newBlock();genesisBlock.setIndex(0);genesisBlock.setTimestamp(newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(newDate()));genesisBlock.setVac(0);genesisBlock.setPrevHash("");genesisBlock.setHash(calculateHash(genesisBlock));blockChain.add(genesisBlock);这里,genesisBlock(创世块)是main函数中最重要的部分,通过它来初始化区块链,毕竟要有一个开始,第一个区块的PrevHash是空的。哦耶!大功告成,开始吧:在终端中,我们可以看到web服务器启动的日志信息,打印出创世块的信息:[INFO]2018-02-0810:58:26SparkWeb@(SparkWeb.java:132):[{"index":0,"timestamp":"2018-02-0810:58:25","vac":0,"hash":"7c2d2db62a82ac8aa3d843ff837c604d8bd17800f4c466d472c5df185b8967fa","prev}Hash":""[信息]2018-02-0810:58:26Log@(Log.java:192):Logginginitialized@1267mstoorg.eclipse.jetty.util.log.Slf4jLog[信息]2018-02-0810:58:26EmbeddedJettyServer@(EmbeddedJettyServer.java:127):==Sparkhasignited...[INFO]2018-02-0810:58:26EmbeddedJettyServer@(EmbeddedJettyServer.java:128):>>Listeningon0.0.0.0:4567[INFO]2018-02-0810:58:26Server@(Server.java:372):jetty-9.4.4.v20170414[INFO]2018-02-0810:58:26DefaultSessionIdManager@(DefaultSessionIdManager.java:364):DefaultSessionIdManagerworkerName=node0[INFO]2018-02-0810:58:26DefaultSessionIdManager@(DefaultSessionIdManager.java:369):NoSessionScavengerset,usingdefaults[信息]2018-02-0810:58:26HouseKeeper@(HouseKeeper.java:149):Scavengingevery600000ms[INFO]2018-02-0810:58:27AbstractConnector@(AbstractConnector.java:280):StartedServerConnector@4c7573c5{HTTP/1.1,[http/1.1]}{0.0.0.0:4567}[INFO]2018-02-0810:58:27Server@(Server.java:444):Started@1669ms然后我们打开浏览器访问http://localhost:4567这个地址,我们可以看到页面显示了当前整个区块链的信息(当然只有一个创世块):{"index":0,"timestamp":"2018-02-0810:58:25","vac":0,"hash":"7c2d2db62a82ac8aa3d843ff837c604d8bd17800f4c466d472c5df185b8967fa","prevHash":""}接下来,我们通过RESRClient发送一些POST请求:posthttp://localhost:4567/{"vac":15}[send];或者使用curl命令:curl-XPOST-ihttp://localhost:4567/--data'{"vac":125}'刷新刚才的http://localhost:4567页面,现在有刚才生成的链块中多了一个,可以看到块的顺序和哈希值都是正确的。源代码:https://github.com/Mignet/blockchain