-
Notifications
You must be signed in to change notification settings - Fork 96
/
witness_proof.js
307 lines (287 loc) · 11.1 KB
/
witness_proof.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/*jslint node: true */
"use strict";
var async = require('async');
var storage = require('./storage.js');
var myWitnesses = require('./my_witnesses.js');
var objectHash = require("./object_hash.js");
var db = require('./db.js');
var constants = require("./constants.js");
var validation = require('./validation.js');
function prepareWitnessProof(arrWitnesses, last_stable_mci, handleResult){
if (typeof last_stable_mci !== 'number')
throw Error('bad last_stable_mci: ' + last_stable_mci);
var arrWitnessChangeAndDefinitionJoints = [];
var arrUnstableMcJoints = [];
var arrLastBallUnits = []; // last ball units referenced from MC-majority-witnessed unstable MC units
var last_ball_unit = null;
var last_ball_mci = null;
async.series([
function(cb){
storage.determineIfWitnessAddressDefinitionsHaveReferences(db, arrWitnesses, function(bWithReferences){
bWithReferences ? cb("some witnesses have references in their addresses, please change your witness list") : cb();
});
},
function(cb){ // collect all unstable MC units
var arrFoundWitnesses = [];
db.query(
"SELECT unit FROM units WHERE +is_on_main_chain=1 AND main_chain_index>? ORDER BY main_chain_index DESC",
[storage.getMinRetrievableMci()],
function(rows){
async.eachSeries(rows, function(row, cb2){
storage.readJointWithBall(db, row.unit, function(objJoint){
delete objJoint.ball; // the unit might get stabilized while we were reading other units
arrUnstableMcJoints.push(objJoint);
for (var i=0; i<objJoint.unit.authors.length; i++){
var address = objJoint.unit.authors[i].address;
if (arrWitnesses.indexOf(address) >= 0 && arrFoundWitnesses.indexOf(address) === -1)
arrFoundWitnesses.push(address);
}
// collect last balls of majority witnessed units
// (genesis lacks last_ball_unit)
if (objJoint.unit.last_ball_unit && arrFoundWitnesses.length >= constants.MAJORITY_OF_WITNESSES && arrLastBallUnits.indexOf(objJoint.unit.last_ball_unit) === -1)
arrLastBallUnits.push(objJoint.unit.last_ball_unit);
cb2();
});
}, cb);
}
);
},
function(cb){ // select the newest last ball unit
if (arrLastBallUnits.length === 0)
return cb("your witness list might be too much off, too few witness authored units");
db.query("SELECT unit, main_chain_index FROM units WHERE unit IN(?) ORDER BY main_chain_index DESC LIMIT 1", [arrLastBallUnits], function(rows){
last_ball_unit = rows[0].unit;
last_ball_mci = rows[0].main_chain_index;
(last_stable_mci >= last_ball_mci) ? cb("already_current") : cb();
});
},
function(cb){ // add definition changes and new definitions of witnesses
var after_last_stable_mci_cond = (last_stable_mci > 0) ? "latest_included_mc_index>="+last_stable_mci : "1";
db.query(
/*"SELECT DISTINCT units.unit \n\
FROM unit_authors \n\
JOIN units USING(unit) \n\
LEFT JOIN address_definition_changes \n\
ON units.unit=address_definition_changes.unit AND unit_authors.address=address_definition_changes.address \n\
WHERE unit_authors.address IN(?) AND "+after_last_stable_mci_cond+" AND is_stable=1 AND sequence='good' \n\
AND (unit_authors.definition_chash IS NOT NULL OR address_definition_changes.unit IS NOT NULL) \n\
ORDER BY `level`",
[arrWitnesses],*/
// 1. initial definitions
// 2. address_definition_changes
// 3. revealing changed definitions
"SELECT unit, `level` \n\
FROM unit_authors "+db.forceIndex('byDefinitionChash')+" \n\
CROSS JOIN units USING(unit) \n\
WHERE definition_chash IN(?) AND definition_chash=address AND "+after_last_stable_mci_cond+" AND is_stable=1 AND sequence='good' \n\
UNION \n\
SELECT unit, `level` \n\
FROM address_definition_changes \n\
CROSS JOIN units USING(unit) \n\
WHERE address_definition_changes.address IN(?) AND "+after_last_stable_mci_cond+" AND is_stable=1 AND sequence='good' \n\
UNION \n\
SELECT units.unit, `level` \n\
FROM address_definition_changes \n\
CROSS JOIN unit_authors USING(address, definition_chash) \n\
CROSS JOIN units ON unit_authors.unit=units.unit \n\
WHERE address_definition_changes.address IN(?) AND "+after_last_stable_mci_cond+" AND is_stable=1 AND sequence='good' \n\
ORDER BY `level`",
[arrWitnesses, arrWitnesses, arrWitnesses],
function(rows){
async.eachSeries(rows, function(row, cb2){
storage.readJoint(db, row.unit, {
ifNotFound: function(){
throw Error("prepareWitnessProof definition changes: not found "+row.unit);
},
ifFound: function(objJoint){
arrWitnessChangeAndDefinitionJoints.push(objJoint);
cb2();
}
});
}, cb);
}
);
}
], function(err){
if (err)
return handleResult(err);
handleResult(null, arrUnstableMcJoints, arrWitnessChangeAndDefinitionJoints, last_ball_unit, last_ball_mci);
});
}
function processWitnessProof(arrUnstableMcJoints, arrWitnessChangeAndDefinitionJoints, bFromCurrent, arrWitnesses, handleResult){
// unstable MC joints
var arrParentUnits = null;
var arrFoundWitnesses = [];
var arrLastBallUnits = [];
var assocLastBallByLastBallUnit = {};
var arrWitnessJoints = [];
for (var i=0; i<arrUnstableMcJoints.length; i++){
var objJoint = arrUnstableMcJoints[i];
var objUnit = objJoint.unit;
if (objJoint.ball)
return handleResult("unstable mc but has ball");
if (!validation.hasValidHashes(objJoint))
return handleResult("invalid hash");
if (arrParentUnits && arrParentUnits.indexOf(objUnit.unit) === -1)
return handleResult("not in parents");
var bAddedJoint = false;
for (var j=0; j<objUnit.authors.length; j++){
var address = objUnit.authors[j].address;
if (arrWitnesses.indexOf(address) >= 0){
if (arrFoundWitnesses.indexOf(address) === -1)
arrFoundWitnesses.push(address);
if (!bAddedJoint)
arrWitnessJoints.push(objJoint);
bAddedJoint = true;
}
}
arrParentUnits = objUnit.parent_units;
if (objUnit.last_ball_unit && arrFoundWitnesses.length >= constants.MAJORITY_OF_WITNESSES){
arrLastBallUnits.push(objUnit.last_ball_unit);
assocLastBallByLastBallUnit[objUnit.last_ball_unit] = objUnit.last_ball;
}
}
if (arrFoundWitnesses.length < constants.MAJORITY_OF_WITNESSES)
return handleResult("not enough witnesses");
if (arrLastBallUnits.length === 0)
throw Error("processWitnessProof: no last ball units");
// changes and definitions of witnesses
for (var i=0; i<arrWitnessChangeAndDefinitionJoints.length; i++){
var objJoint = arrWitnessChangeAndDefinitionJoints[i];
var objUnit = objJoint.unit;
if (!objJoint.ball)
return handleResult("witness_change_and_definition_joints: joint without ball");
if (!validation.hasValidHashes(objJoint))
return handleResult("witness_change_and_definition_joints: invalid hash");
var bAuthoredByWitness = false;
for (var j=0; j<objUnit.authors.length; j++){
var address = objUnit.authors[j].address;
if (arrWitnesses.indexOf(address) >= 0)
bAuthoredByWitness = true;
}
if (!bAuthoredByWitness)
return handleResult("not authored by my witness");
}
var assocDefinitions = {}; // keyed by definition chash
var assocDefinitionChashes = {}; // keyed by address
// checks signatures and updates definitions
function validateUnit(objUnit, bRequireDefinitionOrChange, cb2){
var bFound = false;
async.eachSeries(
objUnit.authors,
function(author, cb3){
var address = author.address;
// if (arrWitnesses.indexOf(address) === -1) // not a witness - skip it
// return cb3();
var definition_chash = assocDefinitionChashes[address];
if (!definition_chash && arrWitnesses.indexOf(address) === -1) // not a witness - skip it
return cb3();
if (!definition_chash)
throw Error("definition chash not known for address "+address+", unit "+objUnit.unit);
if (author.definition){
try{
if (objectHash.getChash160(author.definition) !== definition_chash)
return cb3("definition doesn't hash to the expected value");
}
catch(e){
return cb3("failed to calc definition chash: " +e);
}
assocDefinitions[definition_chash] = author.definition;
bFound = true;
}
function handleAuthor(){
// FIX
validation.validateAuthorSignaturesWithoutReferences(author, objUnit, assocDefinitions[definition_chash], function(err){
if (err)
return cb3(err);
for (var i=0; i<objUnit.messages.length; i++){
var message = objUnit.messages[i];
if (message.app === 'address_definition_change'
&& (message.payload.address === address || objUnit.authors.length === 1 && objUnit.authors[0].address === address)){
assocDefinitionChashes[address] = message.payload.definition_chash;
bFound = true;
}
}
cb3();
});
}
if (assocDefinitions[definition_chash])
return handleAuthor();
storage.readDefinition(db, definition_chash, {
ifFound: function(arrDefinition){
assocDefinitions[definition_chash] = arrDefinition;
handleAuthor();
},
ifDefinitionNotFound: function(d){
throw Error("definition "+definition_chash+" not found, address "+address+", my witnesses "+arrWitnesses.join(', ')+", unit "+objUnit.unit);
}
});
},
function(err){
if (err)
return cb2(err);
if (bRequireDefinitionOrChange && !bFound)
return cb2("neither definition nor change");
cb2();
}
); // each authors
}
var unlock = null;
async.series([
function(cb){ // read latest known definitions of witness addresses
if (!bFromCurrent){
arrWitnesses.forEach(function(address){
assocDefinitionChashes[address] = address;
});
return cb();
}
async.eachSeries(
arrWitnesses,
function(address, cb2){
storage.readDefinitionByAddress(db, address, null, {
ifFound: function(arrDefinition){
var definition_chash = objectHash.getChash160(arrDefinition);
assocDefinitions[definition_chash] = arrDefinition;
assocDefinitionChashes[address] = definition_chash;
cb2();
},
ifDefinitionNotFound: function(definition_chash){
assocDefinitionChashes[address] = definition_chash;
cb2();
}
});
},
cb
);
},
function(cb){ // handle changes of definitions
async.eachSeries(
arrWitnessChangeAndDefinitionJoints,
function(objJoint, cb2){
var objUnit = objJoint.unit;
if (!bFromCurrent)
return validateUnit(objUnit, true, cb2);
db.query("SELECT 1 FROM units WHERE unit=? AND is_stable=1", [objUnit.unit], function(rows){
if (rows.length > 0) // already known and stable - skip it
return cb2();
validateUnit(objUnit, true, cb2);
});
},
cb
); // each change or definition
},
function(cb){ // check signatures of unstable witness joints
async.eachSeries(
arrWitnessJoints.reverse(), // they came in reverse chronological order, reverse() reverses in place
function(objJoint, cb2){
validateUnit(objJoint.unit, false, cb2);
},
cb
);
},
], function(err){
err ? handleResult(err) : handleResult(null, arrLastBallUnits, assocLastBallByLastBallUnit);
});
}
exports.prepareWitnessProof = prepareWitnessProof;
exports.processWitnessProof = processWitnessProof;