【原创】区块链开发:DPoS算法漏洞分析 #C11


#1

前文我们实现了网络模块,并将其加到区块链中,至此我们有了一条可以运行的DPoS链,但是该链是否可靠呢?

答案肯定是存在漏洞的。根据sqfasd同学的分析,由于在每个记账周期内,排序算法的种子是不变的,所以在当前周期内,记账人的顺序是确定的。

如果此时黑客控制一部分节点,然后再对忠诚节点发起DDOS攻击,使其无法响应,那么黑客控制的不连续节点有可能连续地产生区块。当分叉持续到6个区块之后,就可能造成双花。

模拟攻击

这里我们就来模拟一下上面所说的黑客攻击。首先在BlockChain中添加一个可以设置的标志bad,设置为bad的节点就是被黑客控制的节点,轮到怀节点记账时,它会产生分叉。

// blockchain.js

    loop(cb) {
        let self = this;
        if (this.consensus_.prepared()) {
            if (!self.is_bad_) {
                this.generate_block(this.get_account_keypair(), () => {
                    // broadcast block
                    let block = self.get_last_block();
                    console.log(`node: ${self.get_account_id()} generate block! block height: ${block.height} hash: ${block.hash}`);
                });
            } else {
                self.fork();
            }
        }
        cb();
    }

    async fork() {
        console.log('----------fork----------');
        // load transactions
        var tx1 = [{
            amount: 1000,
            recipient: 'bob',
            sender: 'alice'
        }];
        // create block
        let block1 = new Block({
            "keypair": this.get_account_keypair(),
            "previous_block": this.last_block_,
            "transactions": tx1
        }, this.consensus_);
        // make proof of the block/mine
        let self = this;
        let block_data1 = await new Promise((resolve, reject) => {
            block1.on('block completed', (data) => {
                if (data.height == self.last_block_.height + 1) {
                    resolve(data);
                } else {
                    reject('block1 failed');
                }
            });
        });

        // load transactions
        var tx2 = [{
            amount: 1000,
            recipient: 'cracker',
            sender: 'alice'
        }];
        // create block
        let block2 = new Block({
            "keypair": this.get_account_keypair(),
            "previous_block": this.last_block_,
            "transactions": tx2
        }, this.consensus_);
        let block_data2 = await new Promise((resolve, reject) => {
            block2.on('block completed', (data) => {
                if (data.height == self.last_block_.height + 1) {
                    resolve(data);
                } else {
                    reject('block2 failed');
                }
            });
        });

        var i = 0;
        for (var id in this.node_.peers_) {
            let socket = this.node_.peers_[id];
            if (i % 2 == 0) {
                var msg1 = Msg.block(block_data1);
                this.node_.send(socket, msg1);
            } else {
                var msg2 = Msg.block(block_data2);
                this.node_.send(socket, msg2);
            }
            i++;
        }
        console.log("fork");
        this.commit_block(block_data1);
    }

可以看到在fork函数中,节点产生了两个区块,第一个区块中Alice向Bob转了1000币,第二个区块中Alice向Cracker转了1000币,这是一个很明显的双花现象。然后该节点广播区块时,轮流广播两个区块,从而产生分叉。

我们设置节点10为坏节点,然后进行测试:

node: 0 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 1 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 2 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 3 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 4 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 5 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 6 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 7 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 8 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 9 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 10 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 11 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 12 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 13 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 14 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 15 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 16 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 17 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 18 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:a66396:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->
node: 19 (1:d611ed:null) -> (2:3681a8:8) -> (3:71a0c9:9) -> (4:006ebb:10) -> (5:6559da:11) -> (6:2998da:12) -> (7:2965e6:13) ->

(2:3681a8:8)中,2表示区块高度,3681a8表示区块hash值的前6个字符,8表示该区块时哪个节点产生的。从测试结果可以看出,在区块高度为4时产生了分叉,12个节点收到的区块hash值为a66396,剩下8个节点将另一个区块存入链中。在现实情况中,区块链可能产生1到2个分叉,超过3个以上分叉基本是网络分区造成的;而一个节点被控制就可以产生这么多的分叉,可以想象情况有多严重,如果黑客控制6个节点,就可以产生不可逆转的更改。

Pbft+DPoS

为了解决上述问题,我们引入Pbft算法,DPoS算法的前半部分中的出块过程不变,但是后半部分中对区块的验证改为用pbft算法进行全网投票,忠诚的节点不会立即将区块写入链中,而是等待投票数超过一定数量后才会采纳该区块。

// blockchain.js

    commit_block(block_data) {
        if (pbft && !this.is_bad_) {
            var block = new Block();
            block.set_data(block_data);
            let self = this;
            block.on('consensus completed', (data) => {
                self.last_block_ = data;
                self.save_last_block();
            });
            this.pbft_.make_consensus(block);

        } else {
            this.last_block_ = block_data;
            this.save_last_block();
        }
    }

引入Pbft后,再次进行测试,可以看到会出现两种情况。由于节点10广播了两个不同的区块,自己记录区块A,如果其他节点经过投票也选择区块A,那么结果就是情况1,所有链都不产生分叉。如果其他节点经过投票选择了区块B,那么就如情况2所示,只有节点10一个节点产生了分叉,剩余的忠诚节点都保持了一致。

node: 0 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 1 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 2 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 3 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 4 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 5 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 6 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 7 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 8 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 9 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 10 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 11 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 12 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 13 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 14 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 15 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 16 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 17 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 18 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->
node: 19 (1:d611ed:null) -> (2:aab8cb:7) -> (3:05f490:8) -> (4:4d65ee:9) -> (5:26532b:10) -> (6:4d1851:11) ->

node: 0 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 1 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 2 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 3 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 4 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 5 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 6 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 7 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 8 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 9 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 10 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:3021f0:10) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 11 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 12 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 13 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 14 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 15 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 16 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 17 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 18 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->
node: 19 (1:d611ed:null) -> (2:b86fc2:8) -> (3:26b8fe:9) -> (4:e4bee5:11) -> (5:9dce8c:12) -> (6:21ed7b:13) ->

引入Pbft后的DPoS机制就可以低于拜占庭攻击,而不是将这一任务留给社区去完成。

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