proof.ts 10.3 KB
Newer Older
1
2
3
4
import {LOCAL_RULES_HELPERS} from "../../../lib/rules/local_rules"
import {hashf} from "../../../lib/common"
import {DBBlock} from "../../../lib/db/DBBlock"
import {ConfDTO} from "../../../lib/dto/ConfDTO"
5
import {ProverConstants} from "./constants"
6
import {KeyGen} from "../../../lib/common-libs/crypto/keyring"
7
import {dos2unix} from "../../../lib/common-libs/dos2unix"
8
import {rawer} from "../../../lib/common-libs/index"
9
import {ProcessCpuProfiler} from "../../../ProcessCpuProfiler"
10
import {PowDAL} from "../../../lib/dal/fileDALs/PowDAL";
11
12
13

const moment = require('moment');
const querablep = require('querablep');
14
const directory = require('../../../lib/system/directory');
15

16
export function createPowWorker() {
17

18
  let powDAL:PowDAL|null = null
19
20
  let computing = querablep(Promise.resolve(null));
  let askedStop = false;
21

22
23
// By default, we do not prefix the PoW by any number
  let prefix = 0;
24

25
  let signatureFunc:any, lastSecret:any, currentCPU = 1;
26

27
28
29
30
31
32
33
34
  process.on('uncaughtException', (err:any) => {
    console.error(err.stack || Error(err))
    if (process.send) {
      process.send({error: err});
    } else {
      throw Error('process.send() is not defined')
    }
  });
35

36
37
38
  process.on('unhandledRejection', () => {
    process.exit()
  })
39

40
  process.on('message', async (message) => {
41

42
    switch (message.command) {
43

44
45
46
      case 'newPoW':
        (async () => {
          askedStop = true
47

48
49
50
51
          // Very important: do not await if the computation is already done, to keep the lock on JS engine
          if (!computing.isFulfilled()) {
            await computing;
          }
52

53
54
55
56
57
          if (message.value.rootPath) {
            const params = await directory.getHomeFS(false, message.value.rootPath, false)
            powDAL = new PowDAL(message.value.rootPath, params.fs)
          }

58
59
60
61
          const res = await beginNewProofOfWork(message.value);
          answer(message, res);
        })()
        break;
62

63
64
65
66
67
      case 'cancel':
        if (!computing.isFulfilled()) {
          askedStop = true;
        }
        break;
68

69
70
71
72
73
74
75
76
77
      case 'conf':
        if (message.value.cpu !== undefined) {
          currentCPU = message.value.cpu
        }
        if (message.value.prefix !== undefined) {
          prefix = message.value.prefix
        }
        answer(message, { currentCPU, prefix });
        break;
78
79
    }

80
81
82
83
84
  })

  function beginNewProofOfWork(stuff:any) {
    askedStop = false;
    computing = querablep((async () => {
85

86
87
88
      /*****************
       * PREPARE POW STUFF
       ****************/
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
      let nonce = 0;
      const maxDuration = stuff.maxDuration || 1000
      const conf = stuff.conf;
      const block = stuff.block;
      const nonceBeginning = stuff.nonceBeginning;
      const nbZeros = stuff.zeros;
      const pair = stuff.pair;
      const forcedTime = stuff.forcedTime;
      currentCPU = conf.cpu || ProverConstants.DEFAULT_CPU;
      prefix = parseInt(conf.prefix || prefix)
      if (prefix && prefix < ProverConstants.NONCE_RANGE) {
        prefix *= 100 * ProverConstants.NONCE_RANGE
      }
      const highMark = stuff.highMark;
      let sigFunc = null;
      if (signatureFunc && lastSecret === pair.sec) {
        sigFunc = signatureFunc;
      }
      else {
        lastSecret = pair.sec;
        sigFunc = (msg:string) => KeyGen(pair.pub, pair.sec).signSync(msg)
      }
      signatureFunc = sigFunc;
      let pow = "", sig = "", raw = "";
114
115

      /*****************
116
       * GO!
117
118
       ****************/

119
120
121
122
123
124
125
126
127
      let pausePeriod = 1;
      let testsCount = 0;
      let found = false;
      let turn = 0;
      const profiler = new ProcessCpuProfiler(100)
      let cpuUsage = profiler.cpuUsageOverLastMilliseconds(1)
      // We limit the number of tests according to CPU usage
      let testsPerRound = stuff.initialTestsPerRound || 1
      let turnDuration = 20 // We initially goes quickly to the max speed = 50 reevaluations per second (1000 / 20)
128

129
      while (!found && !askedStop) {
130

131
132
133
        /*****************
         * A TURN ~ 100ms
         ****************/
134

135
        await Promise.race([
136

137
138
          // I. Stop the turn if it exceeds `turnDuration` ms
          countDown(turnDuration),
139

140
141
          // II. Process the turn's PoW
          (async () => {
142

143
144
145
            // Prove
            let i = 0;
            const thisTurn = turn;
146

147
148
149
150
151
152
            // Time is updated regularly during the proof
            block.time = getBlockTime(block, conf, forcedTime)
            if (block.number === 0) {
              block.medianTime = block.time
            }
            block.inner_hash = getBlockInnerHash(block);
153
154

            /*****************
155
             * Iterations of a turn
156
157
             ****************/

158
            while(!found && i < testsPerRound && thisTurn === turn && !askedStop) {
159

160
161
              // Nonce change (what makes the PoW change if the time field remains the same)
              nonce++
162

163
164
165
              /*****************
               * A PROOF OF WORK
               ****************/
166

167
168
169
170
171
              // The final nonce is composed of 3 parts
              block.nonce = prefix + nonceBeginning + nonce
              raw = dos2unix("InnerHash: " + block.inner_hash + "\nNonce: " + block.nonce + "\n")
              sig = dos2unix(sigFunc(raw))
              pow = hashf("InnerHash: " + block.inner_hash + "\nNonce: " + block.nonce + "\n" + sig + "\n").toUpperCase()
172

173
174
175
176
177
178
179
180
181
182
183
184
185
186
              /*****************
               * Check the POW result
               ****************/

              let j = 0, charOK = true;
              while (j < nbZeros && charOK) {
                charOK = pow[j] === '0';
                j++;
              }
              if (charOK) {
                found = !!(pow[nbZeros].match(new RegExp('[0-' + highMark + ']')))
              }
              if (!found && nbZeros > 0 && j - 1 >= ProverConstants.POW_MINIMAL_TO_SHOW) {
                pSend({ pow: { pow: pow, block: block, nbZeros: nbZeros }});
187
188
              }

189
190
191
192
193
194
195
196
197
              /*****************
               * - Update local vars
               * - Allow to receive stop signal
               ****************/

              if (!found && !askedStop) {
                i++;
                testsCount++;
                if (i % pausePeriod === 0) {
198
                  await countDown(1); // Very low pause, just the time to process eventual end of the turn
199
200
                }
              }
201
202
203
            }

            /*****************
204
             * Check the POW result
205
             ****************/
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
            if (!found) {

              // CPU speed recording
              if (turn > 0) {
                cpuUsage = profiler.cpuUsageOverLastMilliseconds(turnDuration)
                if (cpuUsage > currentCPU + 0.005 || cpuUsage < currentCPU - 0.005) {
                  let powVariationFactor
                  // powVariationFactor = currentCPU / (cpuUsage || 0.01) / 5 // divide by 2 to avoid extreme responses
                  if (currentCPU > cpuUsage) {
                    powVariationFactor = 1.01
                    testsPerRound = Math.max(1, Math.ceil(testsPerRound * powVariationFactor))
                  } else {
                    powVariationFactor = 0.99
                    testsPerRound = Math.max(1, Math.floor(testsPerRound * powVariationFactor))
                  }
                  pausePeriod = Math.floor(testsPerRound / ProverConstants.POW_NB_PAUSES_PER_ROUND)
                }
              }
224

225
226
227
228
229
230
231
232
233
234
235
              /*****************
               * UNLOAD CPU CHARGE FOR THIS TURN
               ****************/
              // We wait for a maximum time of `turnDuration`.
              // This will trigger the end of the turn by the concurrent race I. During that time, the proof.js script
              // just does nothing: this gives of a bit of breath to the CPU. Tthe amount of "breath" depends on the "cpu"
              // parameter.
              await countDown(turnDuration);
            }
          })()
        ]);
236

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
        // console.log('W#%s.powDAL = ', process.pid, powDAL)

        if (powDAL && !conf.powNoSecurity) {
          const currentProofCheck = await powDAL.getCurrent()
          if (currentProofCheck !== null) {
            if (currentProofCheck === "") {
              askedStop = true
            } else {
              const [currentNumber, currentHash] = currentProofCheck.split('-')
              if (block.number !== parseInt(currentNumber) + 1 || block.previousHash !== currentHash) {
                askedStop = true
              }
            }
          }
        }

253
254
        // Next turn
        turn++
255

256
257
258
        turnDuration += 1
        turnDuration = Math.min(turnDuration, maxDuration) // Max 1 second per turn
      }
259

260
261
262
263
264
265
      /*****************
       * POW IS OVER
       * -----------
       *
       * We either have found a valid POW or a stop event has been detected.
       ****************/
266

267
      if (askedStop) {
268

269
270
271
272
        // PoW stopped
        askedStop = false;
        pSend({ canceled: true })
        return null
273

274
275
276
277
278
279
280
281
282
283
284
      } else {

        // PoW success
        block.hash = pow
        block.signature = sig
        return {
          pow: {
            block: block,
            testsCount: testsCount,
            pow: pow
          }
285
286
        }
      }
287
    })())
288

289
290
    return computing;
  }
291

292
293
294
  function countDown(duration:number) {
    return new Promise((resolve) => setTimeout(resolve, duration));
  }
295

296
297
298
299
  function getBlockInnerHash(block:DBBlock) {
    const raw = rawer.getBlockInnerPart(block);
    return hashf(raw)
  }
300

301
302
303
304
305
306
307
308
309
310
  function getBlockTime (block:DBBlock, conf:ConfDTO, forcedTime:number|null) {
    if (forcedTime) {
      return forcedTime;
    }
    const now = moment.utc().unix();
    const maxAcceleration = LOCAL_RULES_HELPERS.maxAcceleration(conf);
    const timeoffset = block.number >= conf.medianTimeBlocks ? 0 : conf.rootoffset || 0;
    const medianTime = block.medianTime;
    const upperBound = block.number === 0 ? medianTime : Math.min(medianTime + maxAcceleration, now - timeoffset);
    return Math.max(medianTime, upperBound);
311
312
  }

313
314
315
316
317
318
  function answer(message:any, theAnswer:any) {
    return pSend({
      uuid: message.uuid,
      answer: theAnswer
    })
  }
319

320
321
322
323
324
325
326
327
328
329
330
331
  function pSend(stuff:any) {
    return new Promise(function (resolve, reject) {
      if (process.send) {
        process.send(stuff, function (error:any) {
          !error && resolve();
          error && reject();
        })
      } else {
        reject('process.send() is not defined')
      }
    });
  }
332
}