架构理念:[简单][高效][可依赖] 管理理念:[价值][勇气][专注]

只用120行Java代码写一个自己的区块链-2网络

已经看完第一章的内容了吗,欢迎回来。

上一章我们介绍了关于怎么去编写自己的区块链,完成哈希和新块的校验。但是它只是在一个终端(结点)上跑。我们怎么样来连接其他结点以及贡献新的块呢,怎么样广播到其他结点告诉他们要更新区块了呢?

本章就是要告诉你这些。

// 区块链的核心部分
// 维护一个在启动时可以连接的对等节点列表。当一个完整的节点第一次启动时,它必须被自举(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 class BlockUtils {
    /**
     * 计算区块的hash值
     * 
     * @param block
     *            区块
     * @return
     */
    public static String calculateHash(Block block) {
        String record = (block.getIndex()) + block.getTimestamp() + (block.getVac()) + block.getPrevHash();
        MessageDigest digest = DigestUtils.getSha256Digest();
        byte[] hash = digest.digest(StringUtils.getBytesUtf8(record));
        return  Hex.encodeHexString(hash);
    }

    /**
     * 区块的生成
     * 
     * @param oldBlock
     * @param vac
     * @return
     */
    public static Block generateBlock(Block oldBlock, int vac) {
        Block newBlock = new Block();
        newBlock.setIndex(oldBlock.getIndex() + 1);
        newBlock.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        newBlock.setVac(vac);
        newBlock.setPrevHash(oldBlock.getHash());
        newBlock.setHash(calculateHash(newBlock));
        return newBlock;
    }

    /**
     * 校验区块的合法性(有效性)
     * 
     * @param newBlock
     * @param oldBlock
     * @return
     */
    public static boolean isBlockValid(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();
                // 创建一个新的线程 ,和建立连接的结点通讯
                new NodeThread(socket, blockChain).start();

                // 模拟网络结点广播
                new BroadcastThread(socket, blockChain).start();
            }

让我们来写NodeThread的实现。当新的结点连上来,我们发出一条信息“请输入一个资产值”,新结点输入一个数值,创建一个新的块,发送回链,我们将这个新的块加入到链上。

代码如下

     BufferedReader br = null;
        PrintWriter pw = null;
        try {
            //提示结点输入
            pw = new PrintWriter(socket.getOutputStream());
            pw.write("please enter a new number(vac):\n");
            pw.flush();
            String info = null;

            // 读取结点发送的信息
            br = new BufferedReader(new InputStreamReader(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 = new PrintWriter(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)。

posted @ 2018-03-09 17:50  文和-Mignet  阅读(2705)  评论(1编辑  收藏  举报