已经看完第一章的内容了吗,欢迎回来。
上一章我们介绍了关于怎么去编写自己的区块链,完成哈希和新块的校验。但是它只是在一个终端(结点)上跑。我们怎么样来连接其他结点以及贡献新的块呢,怎么样广播到其他结点告诉他们要更新区块了呢?
本章就是要告诉你这些。
// 区块链的核心部分
// 维护一个在启动时可以连接的对等节点列表。当一个完整的节点第一次启动时,它必须被自举(bootstrapped)到网络。
// 自举过程完成后,节点向其对等节点发送一个包含其自身IP地址的addr消息。其对等的每个节点向它们自己的对等节点转发这个信息,以便进一步扩大连接池。
// 块广播
// 在与对等节点建立连接后,双方互发包含最新块哈希值的getblocks消息。
// 如果某个节点坚信其拥有最新的块信息或者有更长的链,它将发送一个inv消息(invite),其中包含至多500个最新块的哈希值,以此来表明它的链更长。
// 收到的节点使用getdata来请求块的详细信息,而远程的节点通过命令block来发送这些信息。
// 在500个块的信息被处理完之后,节点可以通过getblocks请求更多的块信息。这些块在被接收节点认证之后得到确认。
// 新块的确认也可通过矿工挖到并发布的块来发现。其扩散过程和上述类似。
// 通过之前的连接,新块以inv消息发布出去,而接收节点可以通过getdata请求这些块的详细信息。
我们会做什么
1.建立第一个结点,作为tcp server监听
2.打开一个终端,连接到第一个结点上来,模拟新产生区块
3.结点将新的区块链以广播的形式传播到所有结点
我们不会做什么
与上一篇文章一样,本教程的目的是模拟节点网络,以便您可以直观的认识区块链网络。所以不会像真实的区块链网络一样去实现所有功能,对以上功能会进行简化。
让我们开始编码吧!
除了上一章讲过的区块内容,我们将去掉http的部分,今天讲的重点是TCP构建网络
TCP 和 HTTP 有什么差别?
这里我们不会详细讨论,但是您需要知道的是TCP是一个传输数据的基本协议。HTTP建立在TCP之上,以在web和浏览器上使用此数据传输。当您查看一个网站时,您使用的是HTTP,它是由一个名为TCP的底层数据传输协议支持的。
在本教程中,我们将使用TCP,因为我们不需要在浏览器中查看任何内容。
我们创建一个Node.java的文件,来开始我们的编写。
Imports
包声明我们需要的导入。
import java.net.ServerSocket;
import java.net.Socket;
Review
我们的块,以及块相关操作的内容都没有变,索性我们把这些内容放在一起,新建BlockUtils.java文件,内容如下:
public classBlockUtils {/*** 计算区块的hash值
*
*@paramblock
* 区块
*@return
*/
public staticString calculateHash(Block block) {
String record= (block.getIndex()) + block.getTimestamp() + (block.getVac()) +block.getPrevHash();
MessageDigest digest=DigestUtils.getSha256Digest();byte[] hash =digest.digest(StringUtils.getBytesUtf8(record));returnHex.encodeHexString(hash);
}/*** 区块的生成
*
*@paramoldBlock
*@paramvac
*@return
*/
public static Block generateBlock(Block oldBlock, intvac) {
Block newBlock= newBlock();
newBlock.setIndex(oldBlock.getIndex()+ 1);
newBlock.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate()));
newBlock.setVac(vac);
newBlock.setPrevHash(oldBlock.getHash());
newBlock.setHash(calculateHash(newBlock));returnnewBlock;
}/*** 校验区块的合法性(有效性)
*
*@paramnewBlock
*@paramoldBlock
*@return
*/
public static booleanisBlockValid(Block newBlock, Block oldBlock) {if (oldBlock.getIndex() + 1 !=newBlock.getIndex()) {return false;
}if (!oldBlock.getHash().equals(newBlock.getPrevHash())) {return false;
}if (!calculateHash(newBlock).equals(newBlock.getHash())) {return false;
}return true;
}}
好了!我们已经基本得到了所有的区块链相关函数,从第1章删除了所有HTTP相关的内容。我们现在可以进行联网了。
网络
在此之前,让我们声明一个名为bcServer的全局变量(blockchainServer的简称),它是接收传入块的队列。
我们从main函数开始,我们需要把结点作为TCP server启动,监听端口8333。
当网络中有链接进来时,我们接收链接,并开线程处理它。这样如果有很多结点连接过来,每个结点通讯都可以并行处理。
接着,我们模拟广播到全网。
//建立TCP监听8333
serverSocket = new ServerSocket(8333);
LOGGER.info("*** Node is started,waiting for others ***");//监听对等网络中的结点
for(;;) {final Socket socket =serverSocket.accept();//创建一个新的线程 ,和建立连接的结点通讯
newNodeThread(socket, blockChain).start();//模拟网络结点广播
newBroadcastThread(socket, blockChain).start();
}
让我们来写NodeThread的实现。当新的结点连上来,我们发出一条信息“请输入一个资产值”,新结点输入一个数值,创建一个新的块,发送回链,我们将这个新的块加入到链上。
代码如下
BufferedReader br = null;
PrintWriter pw= null;try{//提示结点输入
pw = newPrintWriter(socket.getOutputStream());
pw.write("please enter a new number(vac):\n");
pw.flush();
String info= null;//读取结点发送的信息
br = new BufferedReader(newInputStreamReader(socket.getInputStream()));while ((info = br.readLine()) != null) {try{int vac =Integer.parseInt(info);//根据vac创建区块
Block newBlock = BlockUtils.generateBlock(blockChain.get(blockChain.size() - 1), vac);if (BlockUtils.isBlockValid(newBlock, blockChain.get(blockChain.size() - 1))) {
blockChain.add(newBlock);
pw.write("Success!\n");
pw.write(gson.toJson(blockChain));
}else{
pw.write("HTTP 500: Invalid Block Error\n");
}
LOGGER.info("add new block with vac:" +vac);
}catch(Exception e) {
LOGGER.error("not a number:", e);
pw.write("not a number! \n");
}
pw.write("Please enter a new number(vac):" + "\n");//调用flush()方法将缓冲输出
pw.flush();
}
}catch(Exception e) {
LOGGER.error("TCP i/o error Or client closed", e);
}finally{
LOGGER.info("node closed:" + address.getHostAddress() + ",port:" +socket.getPort());//关闭资源
try{if (pw != null) {
pw.close();
}if (br != null) {
br.close();
}
}catch(IOException e) {
LOGGER.error("close error:", e);
}
}
模拟广播
我们需要把得到的新链告诉所有链接到我们TCPServer的结点,因此,我们将模拟数据如何传输到所有的其他结点。
如何做呢,设置一个时间,定时把blockchain用json化写入连接,通知所有连接结点。
代码如下:
for(;;) {
PrintWriter pw= null;try{
Thread.sleep(30000);
LOGGER.info("\n------------broadcast-------------\n");
LOGGER.info(gson.toJson(blockChain));
pw= newPrintWriter(socket.getOutputStream());//发送到其他结点
pw.write("------------broadcast-------------\n");
pw.write(gson.toJson(blockChain));
pw.flush();
}catch(InterruptedException e) {
LOGGER.error("error:", e);
}catch(IOException e) {
LOGGER.error("error:", e);
}
}
可以到我的github查看完整的代码
https://github.com/Mignet/blockchain
实验步骤:
启动第一个结点.Node
打开一个终端,执行命令nc localhost 8333
打开更多终端,执行命令nc localhost 8333
在任意终端输入数字,看链的打印
每过30秒,看广播到的链。
如果你有真正的网络环境,想象一下,你的任意一台设备作为结点,都可能是第一个结点,它们直接通过tcp互相通讯,广播,是不是也一样类似上面的过程呢。
下一章,我们将谈一谈工作量证明算法(Proof Of Work)。