-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathpbft.js
148 lines (138 loc) · 3.99 KB
/
pbft.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
var assert = require('assert');
var protocol = require('./protocol');
var slots = require('./slots');
var PBFT_N = slots.delegates;
var PBFT_F = Math.floor((PBFT_N - 1) / 3);
var State = {
None: 0,
Prepare: 1,
Commit: 2,
};
function Pbft(blockchain) {
this.blockchain = blockchain;
this.node = blockchain.node;
this.pendingBlocks = {};
this.prepareInfo = null;
this.commitInfos = {};
this.state = State.None;
this.prepareHashCache = {};
this.commitHashCache = {};
this.currentSlot = 0;
}
Pbft.prototype.hasBlock = function(hash) {
return !!this.pendingBlocks[hash];
}
Pbft.prototype.isBusy = function() {
return this.state !== State.None;
}
Pbft.prototype.addBlock = function(block, slot) {
var hash = block.getHash();
console.log('pbft addBlock', this.node.id, hash);
this.pendingBlocks[hash] = block;
if (slot > this.currentSlot) {
this.clearState();
}
if (this.state === State.None) {
this.currentSlot = slot;
this.state = State.Prepare;
this.prepareInfo = {
height: block.getHeight(),
hash: hash,
votesNumber: 1,
votes: {}
};
// TODO will need proof of node signature in formal implementation
this.prepareInfo.votes[this.node.id] = true;
var self = this;
setTimeout(function() {
self.node.broadcast(protocol.prepareMessage({
height: block.getHeight(),
hash: hash,
signer: self.node.id
}));
}, 100);
}
}
Pbft.prototype.clearState = function() {
this.state = State.None;
this.prepareInfo = null;
this.commitInfos = {};
this.pendingBlocks = {};
}
Pbft.prototype.commit = function(hash) {
var block = this.pendingBlocks[hash];
assert(!!block);
this.blockchain.commitBlock(block);
this.clearState();
}
Pbft.prototype.processMessage = function(msg) {
switch (msg.type) {
case protocol.MessageType.Prepare:
var d = msg.body;
var key = d.hash + ':' + d.height + ':' + d.signer;
if (!this.prepareHashCache[key]) {
this.prepareHashCache[key] = true;
this.node.broadcast(msg);
} else {
return;
}
if (this.state === State.Prepare &&
d.height === this.prepareInfo.height &&
d.hash === this.prepareInfo.hash &&
!this.prepareInfo.votes[d.signer]) {
this.prepareInfo.votes[d.signer] = true;
this.prepareInfo.votesNumber++;
console.log('pbft %d prepare votes: %d', this.node.id, this.prepareInfo.votesNumber);
if (this.prepareInfo.votesNumber > PBFT_F) {
console.log('node %d change state to commit', this.node.id);
this.state = State.Commit;
var commitInfo = {
height: this.prepareInfo.height,
hash: this.prepareInfo.hash,
votesNumber: 1,
votes: {}
};
commitInfo.votes[this.node.id] = true;
this.commitInfos[commitInfo.hash] = commitInfo;
this.node.broadcast(protocol.commitMessage({
height: this.prepareInfo.height,
hash: this.prepareInfo.hash,
signer: this.node.id
}));
}
}
break;
case protocol.MessageType.Commit:
var d = msg.body;
var key = d.hash + ':' + d.height + ':' + d.signer;
if (!this.commitHashCache[key]) {
this.commitHashCache[key] = true;
this.node.broadcast(msg);
} else {
return;
}
var commit = this.commitInfos[d.hash];
if (commit) {
if (!commit.votes[d.signer]) {
commit.votes[d.signer] = true;
commit.votesNumber++;
console.log('pbft %d commit votes: %d', this.node.id, commit.votesNumber);
if (commit.votesNumber > 2 * PBFT_F) {
this.commit(d.hash);
}
}
} else {
this.commitInfos[d.hash] = {
hash: d.hash,
height: d.height,
votesNumber: 1,
votes: {}
}
this.commitInfos[d.hash].votes[d.signer] = true;
}
break;
default:
break;
}
}
module.exports = Pbft;