Mise à jour effectuée, merci de nous signaler tout dysfonctionnement ! | Upgrade done, please let us know about any dysfunction!

proof.ts 9.33 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
11
12
13

const moment = require('moment');
const querablep = require('querablep');

14
export function createPowWorker() {
15

16
17
  let computing = querablep(Promise.resolve(null));
  let askedStop = false;
18

19
20
// By default, we do not prefix the PoW by any number
  let prefix = 0;
21

22
  let signatureFunc:any, lastSecret:any, currentCPU = 1;
23

24
25
26
27
28
29
30
31
  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')
    }
  });
32

33
34
35
  process.on('unhandledRejection', () => {
    process.exit()
  })
36

37
  process.on('message', async (message) => {
38

39
    switch (message.command) {
40

41
42
43
      case 'newPoW':
        (async () => {
          askedStop = true
44

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

50
51
52
53
          const res = await beginNewProofOfWork(message.value);
          answer(message, res);
        })()
        break;
54

55
56
57
58
59
      case 'cancel':
        if (!computing.isFulfilled()) {
          askedStop = true;
        }
        break;
60

61
62
63
64
65
66
67
68
69
      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;
70
71
    }

72
73
74
75
76
  })

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

78
79
80
      /*****************
       * PREPARE POW STUFF
       ****************/
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
      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 = "";
106
107

      /*****************
108
       * GO!
109
110
       ****************/

111
112
113
114
115
116
117
118
119
      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)
120

121
      while (!found && !askedStop) {
122

123
124
125
        /*****************
         * A TURN ~ 100ms
         ****************/
126

127
        await Promise.race([
128

129
130
          // I. Stop the turn if it exceeds `turnDuration` ms
          countDown(turnDuration),
131

132
133
          // II. Process the turn's PoW
          (async () => {
134

135
136
137
            // Prove
            let i = 0;
            const thisTurn = turn;
138

139
140
141
142
143
144
            // 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);
145
146

            /*****************
147
             * Iterations of a turn
148
149
             ****************/

150
            while(!found && i < testsPerRound && thisTurn === turn && !askedStop) {
151

152
153
              // Nonce change (what makes the PoW change if the time field remains the same)
              nonce++
154

155
156
157
              /*****************
               * A PROOF OF WORK
               ****************/
158

159
160
161
162
163
              // 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()
164

165
166
167
168
169
170
171
172
173
174
175
176
177
178
              /*****************
               * 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 }});
179
180
              }

181
182
183
184
185
186
187
188
189
              /*****************
               * - Update local vars
               * - Allow to receive stop signal
               ****************/

              if (!found && !askedStop) {
                i++;
                testsCount++;
                if (i % pausePeriod === 0) {
190
                  await countDown(1); // Very low pause, just the time to process eventual end of the turn
191
192
                }
              }
193
194
195
            }

            /*****************
196
             * Check the POW result
197
             ****************/
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
            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)
                }
              }
216

217
218
219
220
221
222
223
224
225
226
227
              /*****************
               * 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);
            }
          })()
        ]);
228

229
230
        // Next turn
        turn++
231

232
233
234
        turnDuration += 1
        turnDuration = Math.min(turnDuration, maxDuration) // Max 1 second per turn
      }
235

236
237
238
239
240
241
      /*****************
       * POW IS OVER
       * -----------
       *
       * We either have found a valid POW or a stop event has been detected.
       ****************/
242

243
      if (askedStop) {
244

245
246
247
248
        // PoW stopped
        askedStop = false;
        pSend({ canceled: true })
        return null
249

250
251
252
253
254
255
256
257
258
259
260
      } else {

        // PoW success
        block.hash = pow
        block.signature = sig
        return {
          pow: {
            block: block,
            testsCount: testsCount,
            pow: pow
          }
261
262
        }
      }
263
    })())
264

265
266
    return computing;
  }
267

268
269
270
  function countDown(duration:number) {
    return new Promise((resolve) => setTimeout(resolve, duration));
  }
271

272
273
274
275
  function getBlockInnerHash(block:DBBlock) {
    const raw = rawer.getBlockInnerPart(block);
    return hashf(raw)
  }
276

277
278
279
280
281
282
283
284
285
286
  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);
287
288
  }

289
290
291
292
293
294
  function answer(message:any, theAnswer:any) {
    return pSend({
      uuid: message.uuid,
      answer: theAnswer
    })
  }
295

296
297
298
299
300
301
302
303
304
305
306
307
  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')
      }
    });
  }
308
}