ucoind 18.3 KB
Newer Older
1
#!/usr/bin/env node
Cédric Moreau's avatar
Cédric Moreau committed
2 3 4 5 6 7 8 9 10 11 12 13
var fs          = require('fs');
var os          = require('os');
var async       = require('async');
var _           = require('underscore');
var program     = require('commander');
var mongoose    = require('mongoose');
var moment      = require('moment');
var inquirer    = require('inquirer');
var wizard      = require('../app/lib/wizard');
var router      = require('../app/lib/streams/router');
var multicaster = require('../app/lib/streams/multicaster');
var logger      = require('../app/lib/logger')('ucoind');
14
var signature   = require('../app/lib/signature');
15 16
var crypto      = require('../app/lib/crypto');
var base58      = require('../app/lib/base58');
17
var constants   = require('../app/lib/constants');
Cédric Moreau's avatar
Cédric Moreau committed
18
var ucoin       = require('./..');
19

Cédric Moreau's avatar
Cédric Moreau committed
20 21 22 23
function keys (val) {
  return val.split(',');
}

24 25 26
// Constants
var DO_NOT_LISTEN_HTTP = false;

27
program
Cédric Moreau's avatar
v0.8.8  
Cédric Moreau committed
28
  .version('0.8.8')
29
  .usage('<command> [options]')
30 31 32 33 34
  .option('-p, --port <port>',         'Port to listen for requests', parseInt)
  .option('-c, --currency <name>',     'Name of the currency managed by this node.')
  .option('--mhost <host>',            'MongoDB host.')
  .option('--mport <port>',            'MongoDB port.')
  .option('-d, --mdb <name>',          'MongoDB database name (defaults to "ucoin_default").')
35 36
  .option('--salt <salt>',             'Key salt to generate this key\'s secret key')
  .option('--passwd <password>',       'Password to generate this key\'s secret key')
37 38 39 40 41 42 43 44 45 46 47 48 49
  .option('--ipv4 <address>',          'IPV4 interface to listen for requests')
  .option('--ipv6 <address>',          'IPV6 interface to listen for requests')
  .option('--remoteh <host>',          'Remote interface others may use to contact this node')
  .option('--remote4 <host>',          'Remote interface for IPv4 access')
  .option('--remote6 <host>',          'Remote interface for IPv6 access')
  .option('--remotep <port>',          'Remote port others may use to contact this node')
  .option('--kmanagement <ALL|KEYS>',  'Define key management policy')
  .option('--kaccept <ALL|KEYS>',      'Define key acceptance policy')
  .option('--sigDelay <timestamp>',    'Minimum delay between 2 certifications of a same issuer for a same recipient, in seconds.')
  .option('--sigValidity <timestamp>', 'Validity duration of a certification, in seconds.')
  .option('--sigQty <number>',         'Minimum number of required certifications to be a member/stay as a member')
  .option('--powZeroMin <number>',     'Minimum number of leading zeros for a proof-of-work')
  .option('--powPeriod <number>',      'Number of blocks to wait to decrease proof-of-work difficulty by one')
50
  .option('--powDelay <number>',       'Number of seconds to wait before starting the computation of next block')
51 52
  .option('--particpate <Y|N>',        'Participate to writing the keychain')
  .option('--tsInterval <number>',     'Number of seconds as acceptable offset when receiving a keyblock')
53 54 55
  .option('--growth <number>',         'Universal Dividend %growth. Aka. \'c\' parameter in RMT')
  .option('--ud0 <number>',            'Universal Dividend initial value')
  .option('--dt <number>',             'Number of seconds between two UD')
56
  .option('--udid2',                   'Enable udid2 format for user id')
57
  ;
Cédric Moreau's avatar
Cédric Moreau committed
58

59 60 61
program
  .command('wizard [step]')
  .description('Launch the configuration Wizard')
62 63 64 65 66 67 68
  .action(function (step) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
69
    });
70
    connect(function (step, server, conf) {
71 72 73 74
      var wiz = wizard(server);
      var task = {
        'currency': wiz.configCurrency,
        'network': wiz.configNetwork,
75 76
        'key': wiz.configKey,
        'ucp': wiz.configUCP
77 78 79 80 81 82 83 84 85 86 87 88 89
      };
      var wizDo = task[step] || wiz.configAll;
      async.waterfall([
        function (next){
          wizDo(conf, next);
        },
        function (next){
          conf.save(function (err) {
            !err && logger.debug("Configuration saved.");
            next(err);
          });
        },
        function (next) {
90
          // Check config
91
          service(ucoin.createPeerServer, function (key, server, conf) {
92 93
            next();
          })(null, null);
94 95 96 97 98 99 100 101
        }
      ], function (err, result) {
        err && logger.error(err);
        server.disconnect();
        process.exit();
      });
    })(step, null);
  });
102

103 104 105
program
  .command('sync [host] [port]')
  .description('Tries to synchronise data with remote uCoin node')
Cédric Moreau's avatar
Cédric Moreau committed
106
  .action(service(ucoin.createTxServer, function (host, port, server, conf) {
107

108 109 110 111
    async.series([
      function (next){
        // Synchronize
        var Synchroniser = require('../app/lib/sync');
112
        var remote = new Synchroniser(server, host, port, false, conf);
113 114 115 116 117 118
        remote.sync(next);
      },
    ], function (err) {
      if(err){
        logger.error('Error during sync:', err);
      }
119
      server.disconnect();
120
      process.exit();
121 122 123
    });
  }));

124 125 126
program
  .command('allow-key [key]')
  .description('Add given key to authorized keys of this node')
127
  .action(service(ucoin.createWOTServer, function (key, server, conf) {
128 129 130 131 132 133 134 135
    key = key || "";
    key = key.toUpperCase();
    async.waterfall([
      function (next) {
        if (!key.isSha1()) {
          next("Key must match a SHA-1 hash");
          return;
        }
136
        next();
137
      },
138
      function (next){
139
        server.KeyService.setKnown(key, next);
140 141 142
      }
    ], function (err, result) {
      if(err){
143
        logger.error('Error: %s', err);
144 145
        server.disconnect();
        process.exit();
146 147
        return;
      }
148
      logger.debug("Key %s is now allowed to be stored" , key);
149
      server.disconnect();
150
      process.exit();
151 152 153
    });
  }));

154 155 156
program
  .command('manage-key [key]')
  .description('Add given key to stack of managed keys of this node')
157
  .action(service(ucoin.createWOTServer, function (key, server, conf) {
158
    handleKey(server, key, true, 'Key %s is now managed');
159 160 161 162 163
  }));

program
  .command('forget-key [key]')
  .description('Remove given key of the managed keys\' stack of this node')
164
  .action(service(ucoin.createWOTServer, function (key, server, conf) {
165
    handleKey(server, key, false, 'Key %s no more managed from now');
166 167
  }));

168
function handleKey (server, key, isManaged, message) {
169 170 171
  key = key || "";
  key = key.toUpperCase();
  async.waterfall([
172
    function (next){
173 174 175 176
      server.initServices(next);
    },
    function (next){
      server.KeyService.handleKey(key, isManaged, next);
177 178 179
    }
  ], function (err, result) {
    if(err){
180
      logger.error('Error: %s', err);
181
      server.disconnect();
182 183
      return;
    }
184
    logger.debug(message , key);
185
    server.disconnect();
186
    process.exit();
187 188 189
  });
}

190 191 192
program
  .command('gen-next [host] [port] [difficulty]')
  .description('Tries to generate the next keyblock of the keychain, with both updates & newcomers')
193
  .action(service(ucoin.createPeerServer, generateAndSend("generateNext")));
194

195 196
program
  .command('gen-empty-next [host] [port] [difficulty]')
197
  .description('Tries to generate the next keyblock of the keychain without any changes')
198
  .action(service(ucoin.createPeerServer, generateAndSend("generateEmptyNext")));
Cédric Moreau's avatar
Cédric Moreau committed
199 200 201

program
  .command('gen-updates [host] [port] [difficulty]')
Cédric Moreau's avatar
Cédric Moreau committed
202
  .description('Tries to generate an update (#1+) keyblock, containing only update changes')
203
  .action(service(ucoin.createPeerServer, generateAndSend("generateUpdates")));
204

205 206
program
  .command('gen-newcomers [host] [port] [difficulty]')
Cédric Moreau's avatar
Cédric Moreau committed
207
  .description('Tries to generate a newcomers (#0+) keyblock, containing only newcomers changes')
208
  .action(service(ucoin.createPeerServer, generateAndSend("generateNewcomers")));
209

210 211
program
  .command('gen-newcomers-auto [host] [port] [difficulty]')
Cédric Moreau's avatar
Cédric Moreau committed
212
  .description('Tries to generate a newcomers (#0+) keyblock, containing only newcomers changes')
213
  .action(service(ucoin.createPeerServer, generateAndSend("generateNewcomersAuto")));
214

Cédric Moreau's avatar
Cédric Moreau committed
215 216 217
program
  .command('compute-distances')
  .description('Recompute distance between each member and the whole WoT and mark outdistanced ones as kicked')
218 219
  .action(service(ucoin.createPeerServer, function (server, conf) {
    server.BlockchainService.computeDistances(function (err) {
Cédric Moreau's avatar
Cédric Moreau committed
220 221 222 223 224 225
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

226 227 228
program
  .command('show-keychain')
  .description('Recompute distance between each member and the whole WoT and mark outdistanced ones as kicked')
229 230
  .action(service(ucoin.createPeerServer, function (server, conf) {
    server.BlockchainService.showKeychain(function (err) {
231 232 233 234 235 236
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

237 238 239
program
  .command('compute-certifications')
  .description('Recompute available certifications for next blocks')
240 241
  .action(service(ucoin.createPeerServer, function (server, conf) {
    server.BlockchainService.updateCertifications(function (err) {
242 243 244 245 246 247
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

248 249
function generateAndSend (generationMethod) {
  return function (host, port, difficulty, server, conf) {
250 251
    async.waterfall([
      function (next){
252
        var method = eval('server.BlockchainService.' + generationMethod);
253
        method(next);
254 255
      },
      function (block, next){
256
        logger.debug('Keyblock to be sent: %s', block.quickDescription());
257
        var wiz = wizard(server);
258
        var doSign = null;
259 260
        async.waterfall([
          function (next){
261
            wiz.configKey(conf, next);
262
          },
263 264
          function (next){
            signature(conf.salt, conf.passwd, next);
265
          },
266 267 268
          function (sigFunc, next){
            doSign = sigFunc;
            // Extract key pair
269
            crypto.getKeyPair(conf.passwd, conf.salt, next);
270
          },
271 272
          function (pair, next){
            proveAndSend(server, block, doSign, base58.encode(pair.publicKey), difficulty, host, port, next);
273 274 275 276 277 278 279 280 281 282
          },
        ], next);
      },
    ], function (err) {
      if (err) {
        logger.error(err);
      }
      server.disconnect();
      process.exit();
    });
283 284
  };
}
285

286 287
function proveAndSend (server, block, sigFunc, issuer, difficulty, host, port, done) {
  var BlockchainService = server.BlockchainService;
288 289
  async.waterfall([
    function (next){
290 291
      block.issuer = issuer;
      BlockchainService.prove(block, sigFunc, difficulty, next);
292 293 294 295 296 297
    },
    function (block, next){
      var Peer = server.conn.model('Peer');
      var peer = new Peer({
        endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
      });
298
      console.log(block.getRawSigned());
299
      logger.info('Posted block ' + block.quickDescription());
300
      multicaster().sendBlock(peer, block, next);
301 302 303 304
    },
  ], done);
}

305 306 307
program
  .command('check-config')
  .description('Checks the node\'s configuration')
308
  .action(service(ucoin.createPeerServer, function (server, conf) {
309 310 311 312 313 314 315
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
316 317 318 319
    });
    return;
  }));

320 321 322
program
  .command('config')
  .description('Register configuration in database')
323
  .action(service(function (server, conf) {
324
    conf.save(function (err) {
325
      if(err){
326
        logger.error("Configuration could not be saved: " + err);
327 328
      }
      else{
329
        logger.debug("Configuration saved.");
330
      }
331
      server.disconnect();
332
      process.exit();
333 334 335
      return;
    });
  }));
336

337 338 339
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
340
  .action(service(function (type, server, conf) {
341
    if(!~['config', 'data'].indexOf(type)){
342
      logger.error('Bad command: usage `reset config` or `reset data`');
343
      server.disconnect();
344 345 346
      return;
    }
    if(type == 'data'){
347
      server.reset(function (err) {
348
        if(err)
349
          logger.error(err);
350 351 352
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
353
        process.exit();
354 355 356
      });
    }
    if(type == 'config'){
357
      server.resetConf(function (err) {
358
        if(err)
359
          logger.error(err);
360 361 362
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
363
        process.exit();
364 365 366 367
      });
    }
  }));

368 369 370
program
  .command('start')
  .description('Start uCoin server using given --currency')
371
  .action(service(ucoin.createPeerServer, function (server, conf) {
372

373 374
    if (conf.udid2) {
      // UserID must match udid2 format
375
      constants.setUDID2Format();
376 377
    }

378 379 380
    // server
    //   .pipe(router(server.PeeringService.cert.fingerprint, server.conn))
    //   .pipe(multicaster());
Cédric Moreau's avatar
Cédric Moreau committed
381

382
    async.waterfall([
383 384 385 386 387 388 389
      // function (next){
      //   server.checkConfig(function (err) {
      //     if (err) {
      //       // If required, launch config
      //       var wiz = wizard(server);
      //       async.waterfall([
      //         function (next){
390
      //           wiz.doTasks(['currency', 'key', 'network'], conf, next);
391 392 393 394 395 396 397 398 399 400 401
      //         },
      //         function (next){
      //           conf.save(next);
      //         },
      //       ], function (err) {
      //         next(err);
      //       });
      //     }
      //     else next();
      //   });
      // },
402 403 404 405 406 407 408
      function (next){
        // Launching server
        server.start(function (err, app) {
          next(err);
        });
      },
    ], function (err) {
409
      if(err){
410 411
        logger.error(err);
        server.disconnect();
412 413 414
        process.exit();
        return;
      }
415
      logger.debug('Server ready!');
416 417 418
    });
  }));

419
function overrideConf(conf) {
420

421 422
  conf.sync = conf.sync || {};
  var cli = {
423
    currency: program.currency,
424 425 426 427
    server: {
      port: program.port,
      ipv4address: program.ipv4,
      ipv6address: program.ipv6,
428 429
      salt: program.salt,
      passwd: program.passwd,
430 431
      remote: {
        host: program.remoteh,
432 433
        ipv4: program.remote4,
        ipv6: program.remote6,
434
        port: program.remotep
435
      }
436 437 438 439 440
    },
    db: {
      host: program.mhost,
      port: program.mport,
      database: program.mdb,
441
    },
442 443
    policy: {
      keys: program.kmanagement,
Cédric Moreau's avatar
Cédric Moreau committed
444
      pubkeys: program.kaccept
445 446 447 448 449 450 451
    },
    ucp: {
      sigDelay:    program.sigDelay,
      sigValidity: program.sigValidity,
      sigQty:      program.sigQty,
      powZeroMin:  program.powZeroMin,
      powPeriod:   program.powPeriod,
452
      powDelay:    program.powDelay,
453
      particpate:  program.particpate,
454 455 456
      tsInterval:  program.tsInterval,
      ud0:         program.ud0,
      c:           program.growth,
457 458
      dt:          program.dt,
      udid2:       program.udid2
459 460
    }
  };
461

462 463 464 465 466
  // Update conf
  if (cli.currency)                         conf.currency       = cli.currency;
  if (cli.server.ipv4address)               conf.ipv4           = cli.server.ipv4address;
  if (cli.server.ipv6address)               conf.ipv6           = cli.server.ipv6address;
  if (cli.server.port)                      conf.port           = cli.server.port;
467 468
  if (cli.server.salt)                      conf.salt           = cli.server.salt;
  if (cli.server.passwd != undefined)       conf.passwd         = cli.server.passwd;
469 470 471 472 473 474
  if (cli.server.remote.host != undefined)  conf.remotehost     = cli.server.remote.host;
  if (cli.server.remote.ipv4 != undefined)  conf.remoteipv4     = cli.server.remote.ipv4;
  if (cli.server.remote.ipv6 != undefined)  conf.remoteipv6     = cli.server.remote.ipv6;
  if (cli.server.remote.port != undefined)  conf.remoteport     = cli.server.remote.port;
  if (cli.policy.keys)                      conf.kmanagement    = cli.policy.keys;
  if (cli.policy.pubkeys)                   conf.kaccept        = cli.policy.pubkeys;
475 476 477 478 479
  if (cli.ucp.sigDelay)                     conf.sigDelay       = cli.ucp.sigDelay;
  if (cli.ucp.sigValidity)                  conf.sigValidity    = cli.ucp.sigValidity;
  if (cli.ucp.sigQty)                       conf.sigQty         = cli.ucp.sigQty;
  if (cli.ucp.powZeroMin)                   conf.powZeroMin     = cli.ucp.powZeroMin;
  if (cli.ucp.powPeriod)                    conf.powPeriod      = cli.ucp.powPeriod;
480
  if (cli.ucp.powDelay)                     conf.powDelay       = cli.ucp.powDelay;
481 482
  if (cli.ucp.participate)                  conf.participate    = cli.ucp.participate == 'Y';
  if (cli.ucp.tsInterval)                   conf.tsInterval     = cli.ucp.tsInterval;
483 484 485
  if (cli.ucp.dt)                           conf.dt             = cli.ucp.dt;
  if (cli.ucp.c)                            conf.c              = cli.ucp.c;
  if (cli.ucp.ud0)                          conf.ud0            = cli.ucp.ud0;
486
  if (cli.ucp.udid2)                        conf.udid2          = cli.ucp.udid2;
487 488 489

  // Specific internal settings
  conf.createNext = true;
490 491
  return conf;
}
492

493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
function connect(callback) {
  return function () {
    var cbArgs = arguments;
    var dbName = program.mdb || "ucoin_default";

    var server = ucoin.connect({ name: dbName, host: program.mhost, port: program.mport }, overrideConf({}));

    // Connecting to DB
    server.on('connected', function (err) {

      if(err){
        logger.warn(err);
        server.disconnect();
        process.exit(1);
        return;
      }

      cbArgs.length--;
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
      callback.apply(this, cbArgs);
    });
  };
}

518
function service(serverFactory, callback) {
519 520
  if (arguments.length == 1) {
    callback = serverFactory;
521
    serverFactory = ucoin.createWOTServer;
522
  }
523 524
  return function () {
    var cbArgs = arguments;
525
    var dbName = program.mdb || "ucoin_default";
526

527
    var server = serverFactory({ name: dbName, host: program.mhost, port: program.mport }, overrideConf({}));
528

529
    // Connecting to DB
530
    server.on('services', function (err) {
531 532

      if(err){
533 534 535
        logger.warn(err);
        server.disconnect();
        process.exit(1);
536 537
        return;
      }
538

539
      cbArgs.length--;
540 541
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
542 543 544 545
      callback.apply(this, cbArgs);
    });
  };
}
546

547 548 549
function parseMoment (d) {
  if (d.toLowerCase() == 'now') {
    return parseInt(moment().format("X"));
550
  } else if (d.match(/^\d{2}-\d{2}-\d{4}$/)) {
551
    return parseInt(moment(d, "DD-MM-YYYY").format("X"));
552 553
  } else if (d.match(/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$/)) {
    return parseInt(moment(d, "DD-MM-YYYY hh:mm:ss").format("X"));
554 555 556 557 558
  } else {
    return parseInt(d);
  }
}

559 560 561 562
function splitColon (str) {
  return str.split(':');
}

563
program.parse(process.argv);