Do a POW (Node)

This commit is contained in:
Kagami Hiiragi 2015-01-10 00:36:42 +03:00
parent 8c799f4e91
commit 225fba30e1
12 changed files with 226 additions and 10 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/npm-debug.log /npm-debug.log
/docs/ /docs/
/worker.browserify.js /worker.browserify.js
/build/

View File

@ -4,3 +4,4 @@
/docs/ /docs/
/jsdoc.json /jsdoc.json
/worker.browserify.js /worker.browserify.js
/build/

View File

@ -49,7 +49,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
- [ ] msg - [ ] msg
- [ ] broadcast - [ ] broadcast
- [x] WIF - [x] WIF
- [ ] POW - [x] POW
- [ ] High-level classes - [ ] High-level classes
- [ ] Address - [ ] Address
- [x] encode - [x] encode

10
binding.gyp Normal file
View File

@ -0,0 +1,10 @@
{
"targets": [
{
"target_name": "worker",
"include_dirs": ["<!(node -e \"require('nan')\")"],
"cflags": ["-Wall", "-O2"],
"sources": ["src/worker.cc", "src/pow.cc"]
}
]
}

View File

@ -26,8 +26,8 @@ exports.randomBytes = function(size) {
return new Buffer(arr); return new Buffer(arr);
}; };
// See `platform.js` for comments.
var B64 = new BN("18446744073709551616"); var B64 = new BN("18446744073709551616");
exports.getTarget = function(opts) { exports.getTarget = function(opts) {
var length = new BN(opts.payloadLength); var length = new BN(opts.payloadLength);
length.iaddn(8); length.iaddn(8);
@ -50,15 +50,19 @@ var FAILBACK_POOL_SIZE = 8;
// because of the WebCryptoAPI (see // because of the WebCryptoAPI (see
// <http://caniuse.com/#feat=cryptography>). // <http://caniuse.com/#feat=cryptography>).
exports.doPOW = function(opts) { exports.doPOW = function(opts) {
// Try to get native cores count then fallback to more or less // Try to get CPU cores count otherwise fallback to default value.
// reasonable value. See <https://stackoverflow.com/q/3289465> for // Currenty navigator's concurrency property available in Chrome and
// details. // not available in Firefox; hope default value won't slow down POW
// speed much on systems with 1 or 2 cores. There are core estimator
// libraries exist (see <https://stackoverflow.com/q/3289465>) but
// they are buggy. Ulimately library user could adjust pool size
// manually.
var poolSize = opts.poolSize || navigator.hardwareConcurrency; var poolSize = opts.poolSize || navigator.hardwareConcurrency;
poolSize = poolSize || FAILBACK_POOL_SIZE; poolSize = poolSize || FAILBACK_POOL_SIZE;
// Check all input params prematurely to not let promise executor or // Check all input params prematurely to not let promise executor or
// worker to fail because of it. // worker to fail because of it.
assert(poolSize > 0, "Bad pool size"); assert(poolSize > 0, "Pool size is too low");
assert(opts.workerUrl, "Bad worker URL"); assert(opts.workerUrl, "Bad worker URL");
assert(typeof opts.target === "number", "Bad target"); assert(typeof opts.target === "number", "Bad target");
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash"); assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
@ -79,8 +83,8 @@ exports.doPOW = function(opts) {
// It's very unlikely that execution will ever reach this place. // It's very unlikely that execution will ever reach this place.
// Currently the only reason why Worker may return value less // Currently the only reason why Worker may return value less
// than zero is a 32-bit nonce overflow (see worker // than zero is a 32-bit nonce overflow (see worker
// implementation). It's more than 4G double hashes. // implementation). It's 4G double hashes.
reject(); reject(new Error("uint32_t nonce overflow"));
} }
} }
@ -115,7 +119,7 @@ exports.doPOW = function(opts) {
reject(e); reject(e);
}; };
}); });
// Allow to stop POW via custom function added to the Promise // Allow to stop a POW via custom function added to the Promise
// instance. // instance.
promise.cancel = cancel; promise.cancel = cancel;
return promise; return promise;

View File

@ -4,9 +4,14 @@
"use strict"; "use strict";
var os = require("os");
var crypto = require("crypto"); var crypto = require("crypto");
var promise = typeof Promise === "undefined" ?
require("es6-promise").Promise :
Promise;
var bignum = require("bignum"); var bignum = require("bignum");
var assert = require("./util").assert; var assert = require("./util").assert;
var worker = require("./worker");
var createHash = crypto.createHash; var createHash = crypto.createHash;
@ -45,3 +50,34 @@ exports.getTarget = function(opts) {
assert(target <= 9007199254740991, "Unsafe target"); assert(target <= 9007199254740991, "Unsafe target");
return target; return target;
}; };
exports.doPOW = function(opts) {
var poolSize = opts.poolSize || os.cpus().length;
// Check all input params prematurely to not let promise executor or
// worker to fail because of it.
// 1 - UINT32_MAX
assert(poolSize > 0, "Pool size is too low");
assert(poolSize <= 4294967295, "Pool size is too high");
// 0 - (2^53 - 1)
assert(typeof opts.target === "number", "Bad target");
assert(opts.target >= 0, "Target is too low");
assert(opts.target <= 9007199254740991, "Target is too high");
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
// TODO(Kagami): Allow to cancel a POW (see `platform.browser.js`).
return new promise(function(resolve, reject) {
worker.powAsync(
poolSize,
opts.target,
opts.initialHash,
function(err, nonce) {
if (err) {
reject(err);
} else {
resolve(nonce);
}
}
);
});
};

12
lib/worker.js Normal file
View File

@ -0,0 +1,12 @@
/**
* Worker routines for Node platform. This module tries to load native
* addon and makes it available for Node.
*/
try {
module.exports = require("../build/Release/worker");
} catch(e) {
// Do nothing for a moment. Everything will work except the functions
// that uses worker routines.
// TODO(Kagami) Provide pure JS fallback.
}

View File

@ -7,6 +7,7 @@
"./lib/platform.js": "./lib/platform.browser.js" "./lib/platform.js": "./lib/platform.browser.js"
}, },
"scripts": { "scripts": {
"install": "node-gyp rebuild || exit 0",
"test": "ALL_TESTS=1 mocha && ALL_TESTS=1 npm run -s kc && ALL_TESTS=1 npm run -s kf && jshint .", "test": "ALL_TESTS=1 mocha && ALL_TESTS=1 npm run -s kc && ALL_TESTS=1 npm run -s kf && jshint .",
"m": "mocha", "m": "mocha",
"k": "npm run -s w && xvfb-run -a karma start", "k": "npm run -s w && xvfb-run -a karma start",
@ -53,7 +54,9 @@
"bs58": "^2.0.0", "bs58": "^2.0.0",
"buffer-equal": "~0.0.1", "buffer-equal": "~0.0.1",
"eccrypto": "^0.1.2", "eccrypto": "^0.1.2",
"es6-promise": "^2.0.1",
"hash.js": "^1.0.2", "hash.js": "^1.0.2",
"nan": "^1.4.1",
"object-assign": "^2.0.0", "object-assign": "^2.0.0",
"sha.js": "^2.3.0" "sha.js": "^2.3.0"
} }

50
src/pow.cc Normal file
View File

@ -0,0 +1,50 @@
// Based on <https://github.com/grant-olson/bitmessage-powfaster>
// fastcpu implementation.
// TODO(Kagami): Port it to WIN32 (see bitmessage-powfaster for an
// example).
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>
#include <openssl/sha.h>
#define HASH_SIZE 64
#define NTOHLL(x) ( ( (uint64_t)(ntohl( (unsigned int)((x << 32) >> 32) )) << 32) | ntohl( ((unsigned int)(x >> 32)) ) )
#define MAX_SAFE_JS_INTEGER 9007199254740991
int pow(uint32_t pool_size,
int64_t target,
const uint8_t* initial_hash,
int64_t* nonce) {
uint8_t message[HASH_SIZE+sizeof(uint64_t)];
uint8_t digest[HASH_SIZE];
uint64_t* be_nonce;
uint64_t* be_trial;
uint64_t i;
SHA512_CTX sha;
memcpy(message+sizeof(uint64_t), initial_hash, HASH_SIZE);
be_nonce = (uint64_t *)message;
be_trial = (uint64_t *)digest;
i = 0;
while (1) {
// This is very unlikely to be ever happen but it's better to be
// sure anyway.
if (i > MAX_SAFE_JS_INTEGER) {
return -1;
}
*be_nonce = NTOHLL(i);
SHA512_Init(&sha);
SHA512_Update(&sha, message, HASH_SIZE+sizeof(uint64_t));
SHA512_Final(digest, &sha);
SHA512_Init(&sha);
SHA512_Update(&sha, digest, HASH_SIZE);
SHA512_Final(digest, &sha);
if (NTOHLL(*be_trial) <= (uint64_t)target) {
break;
}
i++;
}
*nonce = i;
return 0;
}

9
src/pow.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef BITMESSAGE_POW_H
#define BITMESSAGE_POW_H
int pow(uint32_t pool_size,
int64_t target,
const uint8_t* initial_hash,
int64_t* nonce);
#endif

90
src/worker.cc Normal file
View File

@ -0,0 +1,90 @@
#include <node.h>
#include <nan.h>
#include <stdlib.h>
#include "./pow.h"
using node::Buffer;
using v8::Handle;
using v8::Local;
using v8::FunctionTemplate;
using v8::Function;
using v8::Value;
using v8::Object;
using v8::String;
using v8::Integer;
class PowWorker : public NanAsyncWorker {
public:
PowWorker(NanCallback* callback,
uint32_t pool_size,
int64_t target,
uint8_t* initial_hash)
: NanAsyncWorker(callback),
pool_size(pool_size),
target(target),
initial_hash(initial_hash) {}
~PowWorker() {
free(initial_hash);
}
// Executed inside the worker-thread.
// It is not safe to access V8, or V8 data structures
// here, so everything we need for input and output
// should go on `this`.
void Execute () {
error = pow(pool_size, target, initial_hash, &nonce);
}
// Executed when the async work is complete
// this function will be run inside the main event loop
// so it is safe to use V8 again
void HandleOKCallback () {
NanScope();
if (error) {
Local<Value> argv[] = {NanError("Max safe integer overflow")};
callback->Call(1, argv);
} else {
Local<Value> argv[] = {NanNull(), NanNew<Integer>(nonce)};
callback->Call(2, argv);
}
}
private:
uint32_t pool_size;
int64_t target;
uint8_t* initial_hash;
int64_t nonce;
int error;
};
NAN_METHOD(PowAsync) {
NanScope();
NanCallback *callback = new NanCallback(args[3].As<Function>());
uint32_t pool_size = args[0]->Uint32Value();
int64_t target = args[1]->IntegerValue();
size_t length = Buffer::Length(args[2]->ToObject());
char* buf = Buffer::Data(args[2]->ToObject());
uint8_t* initial_hash = (uint8_t *)malloc(length);
if (initial_hash == NULL) {
Local<Value> argv[] = {NanError("Cannot allocate memory")};
callback->Call(1, argv);
} else {
memcpy(initial_hash, buf, length);
NanAsyncQueueWorker(
new PowWorker(callback, pool_size, target, initial_hash));
}
NanReturnUndefined();
}
// Expose synchronous and asynchronous access to our
// Estimate() function
void InitAll(Handle<Object> exports) {
exports->Set(
NanNew<String>("powAsync"),
NanNew<FunctionTemplate>(PowAsync)->GetFunction());
}
NODE_MODULE(worker, InitAll)

View File

@ -279,7 +279,7 @@ describe("POW", function() {
expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false; expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false;
}); });
if (allTests && typeof window !== "undefined") { if (allTests) {
it("should do a POW", function() { it("should do a POW", function() {
this.timeout(120000); this.timeout(120000);
return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")}) return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})