区块链开发:P2P网络 #C10


#1

P2P技术目前已经广泛应用于流媒体、点对点通讯以及文件协同共享等领域,区块链的去中心化特性也少不了其功劳。P2P网络协议有很多,例如BitTorrent、ED2K、Gnutella、Tor等,目前的主流数字货币都实现了自己的P2P网络协议。

P2P协议目前多基于TCP/IP协议,与HTTP协议不同,P2P网络中没有中心化的服务器,完全变成点对点的拓扑结构,每个节点既是服务器,也是客户端。

在实际区块链应用中,不同类型的节点之间交互协议比较复杂,这里我们将其简化,基于前文的区块链结构和DPoS共识,设计一个简单的P2P网络模块。

基本结构

这里我们将基于TCP/IP来实现P2P网络模型,节点默认都是超级节点,而对于普通节点,可以采用同步超级节点的方式,这里不在本文的讨论范围之内。

var net = require("net");
var Msg = require("./message");
var EventEmitter = require('events').EventEmitter;
var Promise = require("bluebird");

var PORT = 8000;

class Node extends EventEmitter {
    constructor(id) {
        super();

        this.id_ = id;
        this.peers_ = {};

        let self = this;
        this.server_ = net.createServer((socket) => {
            socket.setEncoding('utf8');
            socket.on('data', (data) => { self.on_data(data, socket); });
            socket.on('end', () => { self.remove_peer(socket); });
        });
        this.server_.listen(PORT + id);
    }
}

我们使用net模块来进行网络连接,每个节点创建时会自动创建一个TCP server,并监听指定的端口(与节点ID有关)。peers_用于记录当前节点连接的其他节点。

网络模型

P2P网络拓扑有很多种,有些是传统的中心化拓扑结构,有些是半中心化拓扑结构和去中心化拓扑结构。例如比特币的全节点网络就是一个去中心化拓扑结构,节点与节点之间的传输过程更接近“泛洪算法”,即:交易从某个节点产生,接着广播到临近节点,临近节点一传十十传百,直至传播到全网。

我们也采用这种“泛洪算法”来构建一个去中心化的拓扑结构,每个节点启动时会随机连接一些其他的节点,然后发送connection消息,将自己的信息发送给对方,这样对方收到消息后就能把信息加入自己的peers_列表,双方建立连接。

    async start() {
        for (var i = 0; i < 5; ++i) {
            var remote_id = Math.floor(Math.random() * 20); // [0, 20)
            if (remote_id !== this.id_ && !this.peers_[remote_id]) {
                let self = this;
                // console.log(`${this.id_}-->${remote_id}`);
                var socket = net.createConnection({ port: (PORT + remote_id) });
                await new Promise((resolve, reject) => {
                    socket.on('connect', () => {
                        resolve();
                    });
                    socket.on('error', function (e) {
                        resolve();
                    });
                    socket.setEncoding('utf8');
                    socket.on('data', (data) => { self.on_data(data, socket); });
                });
                // console.log(`id: ${self.id_} connected to remote_id: ${remote_id}`);
                let data = Msg.connection(self.id_);
                self.send(socket, data);
                self.add_peer(socket, remote_id);

            }
        }
    }
    on_data(data, socket) {
        try {
            var arr = data.split("\r\n");
            for (var i = 0; i < arr.length; ++i) {
                if (arr[i] == '') continue;
                let obj = JSON.parse(arr[i]);
                if (obj.type == Msg.type.Connection) {
                    // if data is connection info, add peer and response
                    let remote_id = obj.data;
                    this.add_peer(socket, remote_id);
                    // console.log(`node ${this.id_} receive connection: ${remote_id}`);
                    // console.log(`${this.id_}-->${remote_id}`);
                } else {
                    // else emit msg to blockchain
                    this.emit("message", obj);
                }
            }
        } catch (err) {
            console.log(`node: ${this.id_}\t receive msg error`);
            console.log(err);
            console.log(err.message);
            throw new Error();
        }

    }
    send(socket, data) {
        if (typeof data === 'object') {
            data = JSON.stringify(data);
        }
        socket.write(data + "\r\n");
    }
    broadcast(data) {
        for (var index in this.peers_) {
            let socket = this.peers_[index];
            this.send(socket, data);
        }
    }
    add_peer(socket, remote_id) {
        if (!this.peers_[remote_id]) {
            this.peers_[remote_id] = socket;
            // console.log(`${this.id_}-->${remote_id}`);
        }
    }
    remove_peer(socket) {
        for (var index in this.peers_) {
            if (this.peers_[index] == socket) {
                delete this.peers_[index];
                break;
            }
        }
    }

当我们需要广播消息时,也是将消息发送给与当前节点连接的peer节点,peer节点收到消息后继续广播下去。

测试

我们启动20个节点然后让它们轮流记账,得到如下的结果:

node: 15 generate block! block height: 2 hash: 8c35fa69a3d9bc1caac01405dc3f20064e0459a995f1e2f5729043eafe713818
0 --> 2,4,5,6,8,11,12,15,17
1 --> 4,8,10,11,14,16,17,19
2 --> 0,4,5,7,8,9,11,13,15,17
3 --> 5,8,10,13,14,16,18
4 --> 0,1,2,7,16,17
5 --> 0,2,3,7,8,9,14,17,19
6 --> 0,10,12,15,16,18
7 --> 2,4,5,13,14,15,19
8 --> 0,1,2,3,5,9
9 --> 2,5,8,10,11,19
10 --> 1,3,6,9,11,15
11 --> 0,1,2,9,10,13,16,17,18
12 --> 0,6,13,16,17,19
13 --> 2,3,7,11,12,14,15,16
14 --> 1,3,5,7,13,15,16
15 --> 0,2,6,7,10,13,14,19
16 --> 1,3,4,6,11,12,13,14,18,19
17 --> 0,1,2,4,5,11,12
18 --> 3,6,11,16,19
19 --> 1,5,7,9,12,15,16,18
node: 16 generate block! block height: 3 hash: d172ef564cf8ce16fab38b56119ab30592bb9f1ccd5bded21a132b09e56f8276
node: 17 generate block! block height: 4 hash: 50fd1ff6b476136ddd9ed5bd1a9338574283d62067195c075bc60ce68af8b5dd
node: 18 generate block! block height: 2 hash: 596c47634c2040ceeb3ee59e106145636bbcd4b2731695cb5434ad3fe67d0d55
node: 19 generate block! block height: 5 hash: fdceaffcef13b954b8de14b61a7d6a72f4ba34fee2dd2f538fbf9c386e756ab3
node: 0 generate block! block height: 6 hash: 72a47440e80fbf3d3fb639c01f9a918b506126d2cea619020e8dac2f595f69cd
node: 1 generate block! block height: 7 hash: 89feb4100e565764a42f91a1fc865624fdf87a53e09b751c35620456badbb8fa
node: 2 generate block! block height: 8 hash: e826fcaf3bbac63fe5ede98fc68d04366669919f411f7656922247bf4368e9a9
node: 3 generate block! block height: 9 hash: f71da92d0bf329bbc829233039b123bc5a0dae15704f7bfbe3c79f79aff25ed9
node: 4 generate block! block height: 10 hash: 84c6db92500360b19ea604f8e08af42db30b31af32b7af00a420935ca2f3762f
node: 5 generate block! block height: 11 hash: 9417c1038c94dee777453837c3f8fbf891bb050e85b343a98b962d5af05ad818
node: 6 generate block! block height: 12 hash: 114eba02c9da927c2eaea9f51e2aecf948fd06d84732eb2458696b1b1b89be36
node: 7 generate block! block height: 13 hash: 56e1580a6c4667d937e0449607306f0e9e6887778bfc9aadae63ac371cad53fc
node: 8 generate block! block height: 14 hash: bcd96dad4baca6e3d516bfafd5a12f2cda8f70d88b421e8f540454bbf0038472
node: 9 generate block! block height: 15 hash: 798b4a1ab928b8d652188a874a0bd251a8329eefd71700217a8e0718ec0e66ca
node: 10 generate block! block height: 16 hash: 27f87ed2e4f71ecda08382318add3190b30a4e722a643d97415e2c7699bf5e53
node: 11 generate block! block height: 17 hash: 7bc9ea20245121c21180de440eb7473b8e90836799239aa0bcc48c009f0663eb
node: 12 generate block! block height: 18 hash: b070dd8843c84fccf031d404985dc81c039baf0a7ccee19ec0371a8ca75de702
node: 13 generate block! block height: 19 hash: b84df735d880f92b79d82edb647ee124f5bd75b2db7a04d38cbc41434fbd9a01
node: 14 generate block! block height: 20 hash: 0bcbef44f5cc113653544c2cdf69c848e5c274ba52c4fd3de717560d52969e83
node: 15 generate block! block height: 21 hash: 172cc0066707252763bbcfd3e48cfc224a1771922d89348fa446b317f32ca389
node: 16 generate block! block height: 22 hash: 22e5f2df21ba9bf715d793a9ca05150e3c7ef2a273f31225d19ac9d99edea920
node: 17 generate block! block height: 23 hash: 2ef350c5a98b2170453698d6c49f979b6eacc28218e715d8a2cb89f09a33a0ea
node: 18 generate block! block height: 3 hash: 0ab3656c0a5a3121a6883e77cffb4acb1fcd70c22708623eddc03f5b2652e535
node: 19 generate block! block height: 24 hash: f1b85be60c53005e6a29cd5dff47fb227b6adc705c273fd5978ce49c10d97f25
node: 0 generate block! block height: 25 hash: 9f99deb89e91b5a2e3a948235ef72fd1478c3d45af6ff13eb8038941f98f5a4f
node: 1 generate block! block height: 26 hash: ec5af6f40b3e17d1a565a6bbd35c1afccb52d06292b21f8d664d3d1c1de91f58
node: 2 generate block! block height: 27 hash: 1a1522579f241c8a9f72546b2cb5f7a30b4b1f63824b20fffa12edb09ed252ea
node: 3 generate block! block height: 28 hash: 06bc227e39608f563666ba20bbe07b4da592374509be1401e3d11406be790c57
node: 4 generate block! block height: 29 hash: 03b1d47d99a3ae2b2bf32ee64577c4229381a3bded69b3d26c041e58caee43b9
node: 5 generate block! block height: 30 hash: 04f84ffb3928938f48c2467de489904d163c8c89541fb100da835237bcb5cec7
node: 6 generate block! block height: 31 hash: e48a74fa52e5506e467fdc8846748c7afe5177d5e8dbccf5f17b571ed1db3204
node: 7 generate block! block height: 32 hash: 7e8645f5c13b1d3b922e8b3153414a7aaec9f91b0f679b7d3e69ff9b1a314fd7
node: 8 generate block! block height: 33 hash: 47477b4b37b2664e44139df8cd4dd54819285c2913e1a9011cb7b953b9211fb9
node: 9 generate block! block height: 34 hash: 84881a01d9447b2b485df7032f0890c0f8bbe165a2583f9cf60db2aea94c6de2
node: 10 generate block! block height: 35 hash: 7129410f1858b0cf5e4e9ffb5eca589cc9e75420506184056f079dc1d9fb9570
node: 11 generate block! block height: 36 hash: 24b09476a3199868899a432e4d6fe36891b9a8a6c0220d0931489c6702448a38
node: 12 generate block! block height: 37 hash: 0ce11aab702051258c34b4e9057d2a7e82ca2280bcf92f3b6fdfd95c6b569eaa
node: 13 generate block! block height: 38 hash: 9f66a373d374bbafa336c72868a1248037a2e198623de7f27d80d243be7c1122
node: 14 generate block! block height: 39 hash: 51f7c3645cd1b88418c3d8511926cee95f84fdac19db9681905b5efae52495b4
node: 15 generate block! block height: 40 hash: 1c26f42d2901e2deb3eaa66d1ca9b44ffa530fcade5c065b2dacc4ffa3607cc9

可以看到节点18出现了分叉,而其他节点都是按照顺序产生区块。但是查看节点的连接信息,发现节点18并不是孤岛,所以猜测是因为我们节点启动后马上进行挖矿并广播,此时节点之间的P2P网络可能还没有建立好。在这里我们手动将挖矿起始时间延后,等待节点建立完成之后再进行挖矿,可以看到调整后可以避免这种情况。

0 --> 2,4,7,10,11,13,15,18,19
1 --> 5,6,13,14,15,16,18
2 --> 0,8,9,10,14,19
3 --> 8,9,10,12,15,17,18,19
4 --> 0,5,7,9,10,14,18,19
5 --> 1,4,6,11,13,14,15,17,19
6 --> 1,5,8,9
7 --> 0,4,11,14,15,16,18
8 --> 2,3,6,12,13,14
9 --> 2,3,4,6,11,17
10 --> 0,2,3,4,11,19
11 --> 0,5,7,9,10,14,16,17,19
12 --> 3,8,14,15
13 --> 0,1,5,8,14,18
14 --> 1,2,4,5,7,8,11,12,13,16,17,19
15 --> 0,1,3,5,7,12
16 --> 1,7,11,14,18
17 --> 3,5,9,11,14,19
18 --> 0,1,3,4,7,13,16
19 --> 0,2,3,4,5,10,11,14,17
node: 6 generate block! block height: 2 hash: e5fedf091dd4b05d2d07e317a9bd674bc163ee654b1f0ea301c1006b30d48dc4
node: 7 generate block! block height: 3 hash: 2173f58bc3ea5e5aae4cc93054ef6d8337c7c0157419afe45cfb338baecb52c4
node: 8 generate block! block height: 4 hash: a1a7ab3029dcbf1d089bdbe07c7e705bd7ba85b28c6fa4ef84ca457c77479e24
node: 9 generate block! block height: 5 hash: f54bd82698c64fecfa74af43fec1ab2a381738a74c14afa3834f9b40e63f8ee9
node: 10 generate block! block height: 6 hash: 6326cf4d28cb19eaae185b9b09d16277f93a354bf438dbd412876452e302d90c
node: 11 generate block! block height: 7 hash: a124aee38a630df1f4fa1ba9e2a91b212609ba5335070bad6f4be933f8d7410a
node: 12 generate block! block height: 8 hash: 9c398e154e3b3ea1f5a2c9437dcdb20a0c24375491417c69e3700606f2da36f0
node: 13 generate block! block height: 9 hash: 7b04149dc6fbb56c0e13898ec3b62ab5b9e1f7505d7539d1876bfcf8023130a3
node: 14 generate block! block height: 10 hash: a92352436424acf2d14f549ec320f29d32e2d4822c536fbdb754f2946e0a310d
node: 15 generate block! block height: 11 hash: d899e704d17a227532856224697a74a156bb073449a4eec2983f366d087df553
node: 16 generate block! block height: 12 hash: b9da7e670a92e9f0bf6a736e96f31eda9544936198a4b66dec88afa72a9064a7
node: 17 generate block! block height: 13 hash: f7e8f18fd7f6920e47b839cdeea284ec0f10cf942c64730f0cfa88291a9da8b6
node: 18 generate block! block height: 14 hash: dfa413c4bc5494d2dfce1edab844d1375a01ac762a06af108bee4b00d69c0369
node: 19 generate block! block height: 15 hash: b4c9b0afc96c27e79ed8e8b896b882f9b3b598df4c828fd3bbc2946a70d826cb
node: 0 generate block! block height: 16 hash: 7d712544a55aea521d2d057d915904faa42520db818c3efe8d00212d26a0a056
node: 1 generate block! block height: 17 hash: 642523c11e0bf380caa9e558f18b4c136a2cfd27b8f113ea2b9ac4e5a3a4bd3d
node: 2 generate block! block height: 18 hash: 91120db949a2f80891edd897bafcef5eeed6d5bd3c3d243dd19d38e60f83c70b
node: 3 generate block! block height: 19 hash: 1ae79c74cd6f3128cfaa2b6c6d80b0f71d6781d92e8a8bdeba59ef5fa2d10708
node: 4 generate block! block height: 20 hash: b0e6451aa9b718746d7066980bb87024511bff29047068449d40184a52fb3cc7
node: 5 generate block! block height: 21 hash: 7e892f9e83bd65830afae44f5503a7647bef8fd2af44960b0911ff071aa4f364
node: 6 generate block! block height: 22 hash: e359a26a6f77301c64c9e2c2944d3928ee58601847285c3e71a12601047dbd3f
node: 7 generate block! block height: 23 hash: 7d5099b4a818322dd1d22806a09e590da3ca96766075a9c3c015610b6902e458
node: 8 generate block! block height: 24 hash: 09aceaac2c0c0b7405b43cbdfb791ce413f9fccb7cece30cd0c99f92a1ed6520
node: 9 generate block! block height: 25 hash: 1ad072ebb34ed5af8093503dd3e7f8309c947aac91e0e1d91fe56aa79b9150a6
node: 10 generate block! block height: 26 hash: 65a8f100ee513d154487ee7c4cc2bffa0e930ddec7304042d9e0fea32a016a4f
node: 11 generate block! block height: 27 hash: a147bb2db58745800f316de7e8e9d7f717dc112b9ac69dca3d2fe92a3c8d9aa9
node: 12 generate block! block height: 28 hash: a73efe5f39ffccd9060532286beb5418e3c2fde0e7ebffb19da0f9310ea38e8b

在以太坊中,每个节点保持与至少13个其他节点的连接。而在我们的实现中,每个节点都尝试连接5个peer,但由于启动的先后顺序,有的节点被连接时还没有启动,这就会造成连接数进一步减少,一种解决办法时使用ZeroMQ协议来进行热启动连接。

总结

我们这里实现的P2P网络模型只是一个简化了的网络传输模块,只考虑了超级节点的模型。节点的发现过程也是将节点IP硬编码进去的,实际区块链中还分为初始节点和启动后节点发现,还有黑名单与白名单、网络穿透等问题需要考虑,很多的细节问题这里都没有涉及,仅可作为P2P网络最基本的学习使用。

代码地址:https://github.com/yjjnls/awesome-blockchain/tree/v0.1.1/src/js