只用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)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM